@@ -9,33 +9,67 @@ namespace StackExchange.Redis
99    public  readonly  struct  RedisChannel  :  IEquatable < RedisChannel > 
1010    { 
1111        internal  readonly  byte [ ] ?  Value ; 
12-         internal  readonly  bool  IsPatternBased ; 
12+         internal  readonly  bool  _isPatternBased ; 
1313
1414        /// <summary> 
1515        /// Indicates whether the channel-name is either null or a zero-length value. 
1616        /// </summary> 
1717        public  bool  IsNullOrEmpty  =>  Value  ==  null  ||  Value . Length  ==  0 ; 
1818
19+         /// <summary> 
20+         /// Indicates whether this channel represents a wildcard pattern (see <c>PSUBSCRIBE</c>) 
21+         /// </summary> 
22+         public  bool  IsPatternBased  =>  _isPatternBased ; 
23+ 
1924        internal  bool  IsNull  =>  Value  ==  null ; 
2025
26+ 
27+         /// <summary> 
28+         /// Indicates whether channels should use <see cref="PatternMode.Auto"/> when no <see cref="PatternMode"/> 
29+         /// is specified; this is enabled by default, but can be disabled to avoid unexpected wildcard scenarios. 
30+         /// </summary> 
31+         public  static   bool  UseImplicitAutoPattern 
32+         { 
33+             get  =>  s_DefaultPatternMode  ==  PatternMode . Auto ; 
34+             set  =>  s_DefaultPatternMode  =  value  ?  PatternMode . Auto  :  PatternMode . Literal ; 
35+         } 
36+         private  static   PatternMode  s_DefaultPatternMode  =  PatternMode . Auto ; 
37+ 
38+         /// <summary> 
39+         /// Creates a new <see cref="RedisChannel"/> that does not act as a wildcard subscription 
40+         /// </summary> 
41+         public  static   RedisChannel  Literal ( string  value )  =>  new  RedisChannel ( value ,  PatternMode . Literal ) ; 
42+         /// <summary> 
43+         /// Creates a new <see cref="RedisChannel"/> that does not act as a wildcard subscription 
44+         /// </summary> 
45+         public  static   RedisChannel  Literal ( byte [ ]  value )  =>  new  RedisChannel ( value ,  PatternMode . Literal ) ; 
46+         /// <summary> 
47+         /// Creates a new <see cref="RedisChannel"/> that acts as a wildcard subscription 
48+         /// </summary> 
49+         public  static   RedisChannel  Pattern ( string  value )  =>  new  RedisChannel ( value ,  PatternMode . Pattern ) ; 
50+         /// <summary> 
51+         /// Creates a new <see cref="RedisChannel"/> that acts as a wildcard subscription 
52+         /// </summary> 
53+         public  static   RedisChannel  Pattern ( byte [ ]  value )  =>  new  RedisChannel ( value ,  PatternMode . Pattern ) ; 
54+ 
2155        /// <summary> 
2256        /// Create a new redis channel from a buffer, explicitly controlling the pattern mode. 
2357        /// </summary> 
2458        /// <param name="value">The name of the channel to create.</param> 
2559        /// <param name="mode">The mode for name matching.</param> 
26-         public  RedisChannel ( byte [ ] ?  value ,  PatternMode  mode )  :  this ( value ,  DeterminePatternBased ( value ,  mode ) )  { } 
60+         public  RedisChannel ( byte [ ] ?  value ,  PatternMode  mode )  :  this ( value ,  DeterminePatternBased ( value ,  mode ) )  {   } 
2761
2862        /// <summary> 
2963        /// Create a new redis channel from a string, explicitly controlling the pattern mode. 
3064        /// </summary> 
3165        /// <param name="value">The string name of the channel to create.</param> 
3266        /// <param name="mode">The mode for name matching.</param> 
33-         public  RedisChannel ( string  value ,  PatternMode  mode )  :  this ( value  ==  null  ?  null  :  Encoding . UTF8 . GetBytes ( value ) ,  mode )  { } 
67+         public  RedisChannel ( string  value ,  PatternMode  mode )  :  this ( value  ==  null  ?  null  :  Encoding . UTF8 . GetBytes ( value ) ,  mode )  {   } 
3468
3569        private  RedisChannel ( byte [ ] ?  value ,  bool  isPatternBased ) 
3670        { 
3771            Value  =  value ; 
38-             IsPatternBased  =  isPatternBased ; 
72+             _isPatternBased  =  isPatternBased ; 
3973        } 
4074
4175        private  static   bool  DeterminePatternBased ( byte [ ] ?  value ,  PatternMode  mode )  =>  mode  switch 
@@ -87,7 +121,7 @@ private RedisChannel(byte[]? value, bool isPatternBased)
87121        /// <param name="x">The first <see cref="RedisChannel"/> to compare.</param> 
88122        /// <param name="y">The second <see cref="RedisChannel"/> to compare.</param> 
89123        public  static   bool  operator  == ( RedisChannel  x ,  RedisChannel  y )  => 
90-             x . IsPatternBased  ==  y . IsPatternBased  &&  RedisValue . Equals ( x . Value ,  y . Value ) ; 
124+             x . _isPatternBased  ==  y . _isPatternBased  &&  RedisValue . Equals ( x . Value ,  y . Value ) ; 
91125
92126        /// <summary> 
93127        /// Indicate whether two channel names are equal. 
@@ -135,10 +169,10 @@ private RedisChannel(byte[]? value, bool isPatternBased)
135169        /// Indicate whether two channel names are equal. 
136170        /// </summary> 
137171        /// <param name="other">The <see cref="RedisChannel"/> to compare to.</param> 
138-         public  bool  Equals ( RedisChannel  other )  =>  IsPatternBased  ==  other . IsPatternBased  &&  RedisValue . Equals ( Value ,  other . Value ) ; 
172+         public  bool  Equals ( RedisChannel  other )  =>  _isPatternBased  ==  other . _isPatternBased  &&  RedisValue . Equals ( Value ,  other . Value ) ; 
139173
140174        /// <inheritdoc/> 
141-         public  override  int  GetHashCode ( )  =>  RedisValue . GetHashCode ( Value )  +  ( IsPatternBased  ?  1  :  0 ) ; 
175+         public  override  int  GetHashCode ( )  =>  RedisValue . GetHashCode ( Value )  +  ( _isPatternBased  ?  1  :  0 ) ; 
142176
143177        /// <summary> 
144178        /// Obtains a string representation of the channel name. 
@@ -159,7 +193,16 @@ internal void AssertNotNull()
159193            if  ( IsNull )  throw  new  ArgumentException ( "A null key is not valid in this context" ) ; 
160194        } 
161195
162-         internal  RedisChannel  Clone ( )  =>  ( byte [ ] ? ) Value ? . Clone ( )  ??  default ; 
196+         internal  RedisChannel  Clone ( ) 
197+         { 
198+             if  ( Value  is  null  ||  Value . Length  ==  0 ) 
199+             { 
200+                 // no need to duplicate anything 
201+                 return  this ; 
202+             } 
203+             var  copy  =  ( byte [ ] ) Value . Clone ( ) ;  // defensive array copy 
204+             return  new  RedisChannel ( copy ,  _isPatternBased ) ; 
205+         } 
163206
164207        /// <summary> 
165208        /// The matching pattern for this channel. 
@@ -184,33 +227,35 @@ public enum PatternMode
184227        /// Create a channel name from a <see cref="string"/>. 
185228        /// </summary> 
186229        /// <param name="key">The string to get a channel from.</param> 
230+         [ Obsolete ( "It is preferable to explicitly specify a "  +  nameof ( PatternMode )  +  ", or use the "  +  nameof ( Literal )  +  "/"  +  nameof ( Pattern )  +  " methods" ,  error :  false ) ] 
187231        public  static   implicit  operator  RedisChannel ( string  key ) 
188232        { 
189233            if  ( key  ==  null )  return  default ; 
190-             return  new  RedisChannel ( Encoding . UTF8 . GetBytes ( key ) ,  PatternMode . Auto ) ; 
234+             return  new  RedisChannel ( Encoding . UTF8 . GetBytes ( key ) ,  s_DefaultPatternMode ) ; 
191235        } 
192236
193237        /// <summary> 
194238        /// Create a channel name from a <see cref="T:byte[]"/>. 
195239        /// </summary> 
196240        /// <param name="key">The byte array to get a channel from.</param> 
241+         [ Obsolete ( "It is preferable to explicitly specify a "  +  nameof ( PatternMode )  +  ", or use the "  +  nameof ( Literal )  +  "/"  +  nameof ( Pattern )  +  " methods" ,  error :  false ) ] 
197242        public  static   implicit  operator  RedisChannel ( byte [ ] ?  key ) 
198243        { 
199244            if  ( key  ==  null )  return  default ; 
200-             return  new  RedisChannel ( key ,  PatternMode . Auto ) ; 
245+             return  new  RedisChannel ( key ,  s_DefaultPatternMode ) ; 
201246        } 
202247
203248        /// <summary> 
204249        /// Obtain the channel name as a <see cref="T:byte[]"/>. 
205250        /// </summary> 
206251        /// <param name="key">The channel to get a byte[] from.</param> 
207-         public  static   implicit  operator  byte [ ] ?   ( RedisChannel  key )  =>  key . Value ; 
252+         public  static   implicit  operator  byte [ ] ? ( RedisChannel  key )  =>  key . Value ; 
208253
209254        /// <summary> 
210255        /// Obtain the channel name as a <see cref="string"/>. 
211256        /// </summary> 
212257        /// <param name="key">The channel to get a string from.</param> 
213-         public  static   implicit  operator  string ?   ( RedisChannel  key ) 
258+         public  static   implicit  operator  string ? ( RedisChannel  key ) 
214259        { 
215260            var  arr  =  key . Value ; 
216261            if  ( arr  ==  null ) 
@@ -226,5 +271,15 @@ public static implicit operator RedisChannel(byte[]? key)
226271                return  BitConverter . ToString ( arr ) ; 
227272            } 
228273        } 
274+ 
275+ #if DEBUG 
276+         // these exist *purely* to ensure that we never add them later *without* 
277+         // giving due consideration to the default pattern mode (UseImplicitAutoPattern) 
278+         // (since we don't ship them, we don't need them in release) 
279+         [ Obsolete ( "Watch for "  +  nameof ( UseImplicitAutoPattern ) ,  error :  true ) ] 
280+         private  RedisChannel ( string  value )  =>  throw  new  NotSupportedException ( ) ; 
281+         [ Obsolete ( "Watch for "  +  nameof ( UseImplicitAutoPattern ) ,  error :  true ) ] 
282+         private  RedisChannel ( byte [ ] ?  value )  =>  throw  new  NotSupportedException ( ) ; 
283+ #endif
229284    } 
230285} 
0 commit comments