@@ -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