Repository: StackExchange/StackExchange.Redis

Issue:
Suggested analyzer re channel subscribe
scenario; application takes user-supplied channels, and they were using subscribe; they expected discreet channels, but a user-entered channel used a wildcard, and SE.Redis dutifully does a `PSUBSCRIBE` via the default / implicit behaviour ; bad things and possible infosec

I don't think we can change the behaviour at this point without breaking lots of things, however I see a few possible routes forward:

## option 1

a global `public static bool UseImplicitAutoPattern {get;set;} = true;` - if set to true, then the conversion operators and any constructors that do **not** take a `PatternMode` would use `PatternMode.Literal` rather than `PatternMode.Auto`; example:

``` diff
- return new RedisChannel(whatever, PatternMode.Auto);
+ return new RedisChannel(whatever, DefaultPatternMode);
+
+ public static bool UseImplicitAutoPattern {
+     get => DefaultPatternMode == PatternMode.Auto;
+     set => DefaultPatternMode = value ? PatternMode.Auto : PatternMode.Literal;
+ }
+ // keep this simple, since accessed more frequently
+ private static PatternMode DefaultPatternMode {get;set;} = PatternMode.Auto;
```

simple and quick, but not very nuanced - global setting, etc; anyone explicitly using `PatternMode.Auto` would not be impacted; we could also add some helper creation methods

## option 2

we write an analyzer that spots and of the same constructors / operators, **excluding** any scenarios where the input is a literal that does *not* contain a glob-like pattern (so: only non-constant expressions, or literals that are glob-like), that adds an info-level analyzer output saying "hey, you might want to specify whether this is meant to be literal or pattern based"

examples:

``` c#
foo.Subscribe("abc", ...); // no analyzer output; literal and not glob-like
RedisChannel x = "abc"); // no analyzer output; literal and not glob-like

foo.Subscribe("abc*", ...); // add output
RedisChannel x = "abc*"; // add output

foo.Subscribe(someStringChannel, ...); // add output
RedisChannel x = someStringChannel; // add output

foo.Subscribe(some + expression, ...); // add output
RedisChannel x = some + expression; // add output

foo.Publish(literallyAnythingHere, ...); // no analyzer output: publish is never pattern-based
var x = new RedisChannel(literallyAnythingHere, anyPatternModeHere); // they made a choice
```

## option 3

we do option 1 as a quick fix, and follow up with option 2 in due course

## option 4

in combination with option 1, we mark the implicit conversion operators as

``` c#
[Obsolete("You might want to specify a " + nameof(PatternMode), error: false)]
```

and then later un-obsolete them when/if we ship the same via an analyzer (so: "all of the above")

Thoughts...

---

Also included in this topic: expose the "is pattern based" flag via an instance accessor


Hints:

Patch:
diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md
index f60adc161..70b49d1b6 100644
--- a/docs/ReleaseNotes.md
+++ b/docs/ReleaseNotes.md
@@ -8,6 +8,8 @@ Current package versions:
 
 ## Unreleased
 
+- Fix [#2479](https://github.com/StackExchange/StackExchange.Redis/issues/2479): Add `RedisChannel.UseImplicitAutoPattern` (global) and `RedisChannel.IsPatternBased` ([#2480 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2480))
+- Fix [#2479](https://github.com/StackExchange/StackExchange.Redis/issues/2479): Mark `RedisChannel` conversion operators as obsolete; add `RedisChannel.Literal` and `RedisChannel.Pattern` helpers ([#2481 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2481))
 - Fix [#2449](https://github.com/StackExchange/StackExchange.Redis/issues/2449): Update `Pipelines.Sockets.Unofficial` to `v2.2.8` to support native AOT ([#2456 by eerhardt](https://github.com/StackExchange/StackExchange.Redis/pull/2456))
 
 ## 2.6.111
diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs
index e9f01264f..da30beb56 100644
--- a/src/StackExchange.Redis/ConfigurationOptions.cs
+++ b/src/StackExchange.Redis/ConfigurationOptions.cs
@@ -860,7 +860,7 @@ private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown)
                             ClientName = value;
                             break;
                         case OptionKeys.ChannelPrefix:
-                            ChannelPrefix = value;
+                            ChannelPrefix = RedisChannel.Literal(value);
                             break;
                         case OptionKeys.ConfigChannel:
                             ConfigurationChannel = value;
diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs
index 302bccea9..145826e3c 100644
--- a/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs
+++ b/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs
@@ -1,11 +1,11 @@
-﻿using System;
+﻿using Pipelines.Sockets.Unofficial;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
-using Pipelines.Sockets.Unofficial;
 
 namespace StackExchange.Redis;
 
@@ -30,9 +30,9 @@ internal void InitializeSentinel(LogProxy? logProxy)
         // Subscribe to sentinel change events
         ISubscriber sub = GetSubscriber();
 
-        if (sub.SubscribedEndpoint("+switch-master") == null)
+        if (sub.SubscribedEndpoint(RedisChannel.Literal("+switch-master")) == null)
         {
-            sub.Subscribe("+switch-master", (__, message) =>
+            sub.Subscribe(RedisChannel.Literal("+switch-master"), (__, message) =>
             {
                 string[] messageParts = ((string)message!).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                 // We don't care about the result of this - we're just trying
@@ -68,9 +68,9 @@ internal void InitializeSentinel(LogProxy? logProxy)
             ReconfigureAsync(first: false, reconfigureAll: true, logProxy, e.EndPoint, "Lost sentinel connection", false).Wait();
 
         // Subscribe to new sentinels being added
-        if (sub.SubscribedEndpoint("+sentinel") == null)
+        if (sub.SubscribedEndpoint(RedisChannel.Literal("+sentinel")) == null)
         {
-            sub.Subscribe("+sentinel", (_, message) =>
+            sub.Subscribe(RedisChannel.Literal("+sentinel"), (_, message) =>
             {
                 string[] messageParts = ((string)message!).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                 UpdateSentinelAddressList(messageParts[0]);
diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.cs b/src/StackExchange.Redis/ConnectionMultiplexer.cs
index 37d45be09..88c6c37c2 100644
--- a/src/StackExchange.Redis/ConnectionMultiplexer.cs
+++ b/src/StackExchange.Redis/ConnectionMultiplexer.cs
@@ -2221,7 +2221,7 @@ public long PublishReconfigure(CommandFlags flags = CommandFlags.None)
 
         private long PublishReconfigureImpl(CommandFlags flags) =>
             ConfigurationChangedChannel is byte[] channel
-                ? GetSubscriber().Publish(channel, RedisLiterals.Wildcard, flags)
+                ? GetSubscriber().Publish(RedisChannel.Literal(channel), RedisLiterals.Wildcard, flags)
                 : 0;
 
         /// <summary>
@@ -2231,7 +2231,7 @@ ConfigurationChangedChannel is byte[] channel
         /// <returns>The number of instances known to have received the message (however, the actual number can be higher).</returns>
         public Task<long> PublishReconfigureAsync(CommandFlags flags = CommandFlags.None) =>
             ConfigurationChangedChannel is byte[] channel
-                ? GetSubscriber().PublishAsync(channel, RedisLiterals.Wildcard, flags)
+                ? GetSubscriber().PublishAsync(RedisChannel.Literal(channel), RedisLiterals.Wildcard, flags)
                 : CompletedTask<long>.Default(null);
 
         /// <summary>
diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
index 290fbed59..3bad77d43 100644
--- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
+++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
@@ -841,8 +841,11 @@ protected RedisValue SortGetToInner(RedisValue outer) =>
             }
         }
 
-        protected RedisChannel ToInner(RedisChannel outer) =>
-            RedisKey.ConcatenateBytes(Prefix, null, (byte[]?)outer);
+        protected RedisChannel ToInner(RedisChannel outer)
+        {
+            var combined = RedisKey.ConcatenateBytes(Prefix, null, (byte[]?)outer);
+            return new RedisChannel(combined, outer.IsPatternBased ? RedisChannel.PatternMode.Pattern : RedisChannel.PatternMode.Literal);
+        }
 
         private Func<RedisKey, RedisKey>? mapFunction;
         protected Func<RedisKey, RedisKey> GetMapFunction() =>
diff --git a/src/StackExchange.Redis/Maintenance/AzureMaintenanceEvent.cs b/src/StackExchange.Redis/Maintenance/AzureMaintenanceEvent.cs
index c965e1231..330b27683 100644
--- a/src/StackExchange.Redis/Maintenance/AzureMaintenanceEvent.cs
+++ b/src/StackExchange.Redis/Maintenance/AzureMaintenanceEvent.cs
@@ -130,7 +130,7 @@ internal async static Task AddListenerAsync(ConnectionMultiplexer multiplexer, A
                     return;
                 }
 
-                await sub.SubscribeAsync(PubSubChannelName, async (_, message) =>
+                await sub.SubscribeAsync(RedisChannel.Literal(PubSubChannelName), async (_, message) =>
                 {
                     var newMessage = new AzureMaintenanceEvent(message!);
                     newMessage.NotifyMultiplexer(multiplexer);
diff --git a/src/StackExchange.Redis/PhysicalBridge.cs b/src/StackExchange.Redis/PhysicalBridge.cs
index e7af56a69..b42c40a19 100644
--- a/src/StackExchange.Redis/PhysicalBridge.cs
+++ b/src/StackExchange.Redis/PhysicalBridge.cs
@@ -373,7 +373,7 @@ internal void KeepAlive()
                     else if (commandMap.IsAvailable(RedisCommand.UNSUBSCRIBE))
                     {
                         msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.UNSUBSCRIBE,
-                            (RedisChannel)Multiplexer.UniqueId);
+                            RedisChannel.Literal(Multiplexer.UniqueId));
                         msg.SetSource(ResultProcessor.TrackSubscriptions, null);
                     }
                     break;
diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
index ccfb4edb1..464af8023 100644
--- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
+++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
@@ -1259,6 +1259,7 @@ StackExchange.Redis.Proxy.Twemproxy = 1 -> StackExchange.Redis.Proxy
 StackExchange.Redis.RedisChannel
 StackExchange.Redis.RedisChannel.Equals(StackExchange.Redis.RedisChannel other) -> bool
 StackExchange.Redis.RedisChannel.IsNullOrEmpty.get -> bool
+StackExchange.Redis.RedisChannel.IsPatternBased.get -> bool
 StackExchange.Redis.RedisChannel.PatternMode
 StackExchange.Redis.RedisChannel.PatternMode.Auto = 0 -> StackExchange.Redis.RedisChannel.PatternMode
 StackExchange.Redis.RedisChannel.PatternMode.Literal = 1 -> StackExchange.Redis.RedisChannel.PatternMode
@@ -1657,6 +1658,8 @@ static StackExchange.Redis.RedisChannel.implicit operator byte[]?(StackExchange.
 static StackExchange.Redis.RedisChannel.implicit operator StackExchange.Redis.RedisChannel(byte[]? key) -> StackExchange.Redis.RedisChannel
 static StackExchange.Redis.RedisChannel.implicit operator StackExchange.Redis.RedisChannel(string! key) -> StackExchange.Redis.RedisChannel
 static StackExchange.Redis.RedisChannel.implicit operator string?(StackExchange.Redis.RedisChannel key) -> string?
+static StackExchange.Redis.RedisChannel.Literal(byte[]! value) -> StackExchange.Redis.RedisChannel
+static StackExchange.Redis.RedisChannel.Literal(string! value) -> StackExchange.Redis.RedisChannel
 static StackExchange.Redis.RedisChannel.operator !=(byte[]! x, StackExchange.Redis.RedisChannel y) -> bool
 static StackExchange.Redis.RedisChannel.operator !=(StackExchange.Redis.RedisChannel x, byte[]! y) -> bool
 static StackExchange.Redis.RedisChannel.operator !=(StackExchange.Redis.RedisChannel x, StackExchange.Redis.RedisChannel y) -> bool
@@ -1667,6 +1670,10 @@ static StackExchange.Redis.RedisChannel.operator ==(StackExchange.Redis.RedisCha
 static StackExchange.Redis.RedisChannel.operator ==(StackExchange.Redis.RedisChannel x, StackExchange.Redis.RedisChannel y) -> bool
 static StackExchange.Redis.RedisChannel.operator ==(StackExchange.Redis.RedisChannel x, string! y) -> bool
 static StackExchange.Redis.RedisChannel.operator ==(string! x, StackExchange.Redis.RedisChannel y) -> bool
+static StackExchange.Redis.RedisChannel.Pattern(byte[]! value) -> StackExchange.Redis.RedisChannel
+static StackExchange.Redis.RedisChannel.Pattern(string! value) -> StackExchange.Redis.RedisChannel
+static StackExchange.Redis.RedisChannel.UseImplicitAutoPattern.get -> bool
+static StackExchange.Redis.RedisChannel.UseImplicitAutoPattern.set -> void
 static StackExchange.Redis.RedisFeatures.operator !=(StackExchange.Redis.RedisFeatures left, StackExchange.Redis.RedisFeatures right) -> bool
 static StackExchange.Redis.RedisFeatures.operator ==(StackExchange.Redis.RedisFeatures left, StackExchange.Redis.RedisFeatures right) -> bool
 static StackExchange.Redis.RedisKey.implicit operator byte[]?(StackExchange.Redis.RedisKey key) -> byte[]?
diff --git a/src/StackExchange.Redis/RedisChannel.cs b/src/StackExchange.Redis/RedisChannel.cs
index ffd56aed4..16d4e7107 100644
--- a/src/StackExchange.Redis/RedisChannel.cs
+++ b/src/StackExchange.Redis/RedisChannel.cs
@@ -9,33 +9,67 @@ namespace StackExchange.Redis
     public readonly struct RedisChannel : IEquatable<RedisChannel>
     {
         internal readonly byte[]? Value;
-        internal readonly bool IsPatternBased;
+        internal readonly bool _isPatternBased;
 
         /// <summary>
         /// Indicates whether the channel-name is either null or a zero-length value.
         /// </summary>
         public bool IsNullOrEmpty => Value == null || Value.Length == 0;
 
+        /// <summary>
+        /// Indicates whether this channel represents a wildcard pattern (see <c>PSUBSCRIBE</c>)
+        /// </summary>
+        public bool IsPatternBased => _isPatternBased;
+
         internal bool IsNull => Value == null;
 
+
+        /// <summary>
+        /// Indicates whether channels should use <see cref="PatternMode.Auto"/> when no <see cref="PatternMode"/>
+        /// is specified; this is enabled by default, but can be disabled to avoid unexpected wildcard scenarios.
+        /// </summary>
+        public static bool UseImplicitAutoPattern
+        {
+            get => s_DefaultPatternMode == PatternMode.Auto;
+            set => s_DefaultPatternMode = value ? PatternMode.Auto : PatternMode.Literal;
+        }
+        private static PatternMode s_DefaultPatternMode = PatternMode.Auto;
+
+        /// <summary>
+        /// Creates a new <see cref="RedisChannel"/> that does not act as a wildcard subscription
+        /// </summary>
+        public static RedisChannel Literal(string value) => new RedisChannel(value, PatternMode.Literal);
+        /// <summary>
+        /// Creates a new <see cref="RedisChannel"/> that does not act as a wildcard subscription
+        /// </summary>
+        public static RedisChannel Literal(byte[] value) => new RedisChannel(value, PatternMode.Literal);
+        /// <summary>
+        /// Creates a new <see cref="RedisChannel"/> that acts as a wildcard subscription
+        /// </summary>
+        public static RedisChannel Pattern(string value) => new RedisChannel(value, PatternMode.Pattern);
+        /// <summary>
+        /// Creates a new <see cref="RedisChannel"/> that acts as a wildcard subscription
+        /// </summary>
+        public static RedisChannel Pattern(byte[] value) => new RedisChannel(value, PatternMode.Pattern);
+
         /// <summary>
         /// Create a new redis channel from a buffer, explicitly controlling the pattern mode.
         /// </summary>
         /// <param name="value">The name of the channel to create.</param>
         /// <param name="mode">The mode for name matching.</param>
-        public RedisChannel(byte[]? value, PatternMode mode) : this(value, DeterminePatternBased(value, mode)) {}
+        public RedisChannel(byte[]? value, PatternMode mode) : this(value, DeterminePatternBased(value, mode)) { }
 
         /// <summary>
         /// Create a new redis channel from a string, explicitly controlling the pattern mode.
         /// </summary>
         /// <param name="value">The string name of the channel to create.</param>
         /// <param name="mode">The mode for name matching.</param>
-        public RedisChannel(string value, PatternMode mode) : this(value == null ? null : Encoding.UTF8.GetBytes(value), mode) {}
+        public RedisChannel(string value, PatternMode mode) : this(value == null ? null : Encoding.UTF8.GetBytes(value), mode) { }
 
         private RedisChannel(byte[]? value, bool isPatternBased)
         {
             Value = value;
-            IsPatternBased = isPatternBased;
+            _isPatternBased = isPatternBased;
         }
 
         private static bool DeterminePatternBased(byte[]? value, PatternMode mode) => mode switch
@@ -87,7 +121,7 @@ private RedisChannel(byte[]? value, bool isPatternBased)
         /// <param name="x">The first <see cref="RedisChannel"/> to compare.</param>
         /// <param name="y">The second <see cref="RedisChannel"/> to compare.</param>
         public static bool operator ==(RedisChannel x, RedisChannel y) =>
-            x.IsPatternBased == y.IsPatternBased && RedisValue.Equals(x.Value, y.Value);
+            x._isPatternBased == y._isPatternBased && RedisValue.Equals(x.Value, y.Value);
 
         /// <summary>
         /// Indicate whether two channel names are equal.
@@ -135,10 +169,10 @@ private RedisChannel(byte[]? value, bool isPatternBased)
         /// Indicate whether two channel names are equal.
         /// </summary>
         /// <param name="other">The <see cref="RedisChannel"/> to compare to.</param>
-        public bool Equals(RedisChannel other) => IsPatternBased == other.IsPatternBased && RedisValue.Equals(Value, other.Value);
+        public bool Equals(RedisChannel other) => _isPatternBased == other._isPatternBased && RedisValue.Equals(Value, other.Value);
 
         /// <inheritdoc/>
-        public override int GetHashCode() => RedisValue.GetHashCode(Value) + (IsPatternBased ? 1 : 0);
+        public override int GetHashCode() => RedisValue.GetHashCode(Value) + (_isPatternBased ? 1 : 0);
 
         /// <summary>
         /// Obtains a string representation of the channel name.
@@ -159,7 +193,16 @@ internal void AssertNotNull()
             if (IsNull) throw new ArgumentException("A null key is not valid in this context");
         }
 
-        internal RedisChannel Clone() => (byte[]?)Value?.Clone() ?? default;
+        internal RedisChannel Clone()
+        {
+            if (Value is null || Value.Length == 0)
+            {
+                // no need to duplicate anything
+                return this;
+            }
+            var copy = (byte[])Value.Clone(); // defensive array copy
+            return new RedisChannel(copy, _isPatternBased);
+        }
 
         /// <summary>
         /// The matching pattern for this channel.
@@ -184,33 +227,35 @@ public enum PatternMode
         /// Create a channel name from a <see cref="string"/>.
         /// </summary>
         /// <param name="key">The string to get a channel from.</param>
+        [Obsolete("It is preferable to explicitly specify a " + nameof(PatternMode) + ", or use the " + nameof(Literal) + "/" + nameof(Pattern) + " methods", error: false)]
         public static implicit operator RedisChannel(string key)
         {
             if (key == null) return default;
-            return new RedisChannel(Encoding.UTF8.GetBytes(key), PatternMode.Auto);
+            return new RedisChannel(Encoding.UTF8.GetBytes(key), s_DefaultPatternMode);
         }
 
         /// <summary>
         /// Create a channel name from a <see cref="T:byte[]"/>.
         /// </summary>
         /// <param name="key">The byte array to get a channel from.</param>
+        [Obsolete("It is preferable to explicitly specify a " + nameof(PatternMode) + ", or use the " + nameof(Literal) + "/" + nameof(Pattern) + " methods", error: false)]
         public static implicit operator RedisChannel(byte[]? key)
         {
             if (key == null) return default;
-            return new RedisChannel(key, PatternMode.Auto);
+            return new RedisChannel(key, s_DefaultPatternMode);
         }
 
         /// <summary>
         /// Obtain the channel name as a <see cref="T:byte[]"/>.
         /// </summary>
         /// <param name="key">The channel to get a byte[] from.</param>
-        public static implicit operator byte[]? (RedisChannel key) => key.Value;
+        public static implicit operator byte[]?(RedisChannel key) => key.Value;
 
         /// <summary>
         /// Obtain the channel name as a <see cref="string"/>.
         /// </summary>
         /// <param name="key">The channel to get a string from.</param>
-        public static implicit operator string? (RedisChannel key)
+        public static implicit operator string?(RedisChannel key)
         {
             var arr = key.Value;
             if (arr == null)
@@ -226,5 +271,15 @@ public static implicit operator RedisChannel(byte[]? key)
                 return BitConverter.ToString(arr);
             }
         }
+
+#if DEBUG
+        // these exist *purely* to ensure that we never add them later *without*
+        // giving due consideration to the default pattern mode (UseImplicitAutoPattern)
+        // (since we don't ship them, we don't need them in release)
+        [Obsolete("Watch for " + nameof(UseImplicitAutoPattern), error: true)]
+        private RedisChannel(string value) => throw new NotSupportedException();
+        [Obsolete("Watch for " + nameof(UseImplicitAutoPattern), error: true)]
+        private RedisChannel(byte[]? value) => throw new NotSupportedException();
+#endif
     }
 }
diff --git a/src/StackExchange.Redis/RedisSubscriber.cs b/src/StackExchange.Redis/RedisSubscriber.cs
index d26368c20..39b99bfe2 100644
--- a/src/StackExchange.Redis/RedisSubscriber.cs
+++ b/src/StackExchange.Redis/RedisSubscriber.cs
@@ -159,7 +159,7 @@ public Subscription(CommandFlags flags)
             /// </summary>
             internal Message GetMessage(RedisChannel channel, SubscriptionAction action, CommandFlags flags, bool internalCall)
             {
-                var isPattern = channel.IsPatternBased;
+                var isPattern = channel._isPatternBased;
                 var command = action switch
                 {
                     SubscriptionAction.Subscribe when isPattern => RedisCommand.PSUBSCRIBE,
diff --git a/src/StackExchange.Redis/ServerEndPoint.cs b/src/StackExchange.Redis/ServerEndPoint.cs
index 36163578d..d3082e35c 100644
--- a/src/StackExchange.Redis/ServerEndPoint.cs
+++ b/src/StackExchange.Redis/ServerEndPoint.cs
@@ -979,7 +979,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, LogProxy? log)
                 var configChannel = Multiplexer.ConfigurationChangedChannel;
                 if (configChannel != null)
                 {
-                    msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.SUBSCRIBE, (RedisChannel)configChannel);
+                    msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.SUBSCRIBE, RedisChannel.Literal(configChannel));
                     // Note: this is NOT internal, we want it to queue in a backlog for sending when ready if necessary
                     await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.TrackSubscriptions).ForAwait();
                 }


Test patch:
diff --git a/tests/ConsoleTest/Program.cs b/tests/ConsoleTest/Program.cs
index 1d33a968e..0d6fa7e13 100644
--- a/tests/ConsoleTest/Program.cs
+++ b/tests/ConsoleTest/Program.cs
@@ -110,7 +110,7 @@ static void ParallelRun(int taskId, ConnectionMultiplexer connection)
 static void MassPublish(ConnectionMultiplexer connection)
 {
     var subscriber = connection.GetSubscriber();
-    Parallel.For(0, 1000, _ => subscriber.Publish("cache-events:cache-testing", "hey"));
+    Parallel.For(0, 1000, _ => subscriber.Publish(new RedisChannel("cache-events:cache-testing", RedisChannel.PatternMode.Literal), "hey"));
 }
 
 static string GetLibVersion()
diff --git a/tests/StackExchange.Redis.Tests/ChannelTests.cs b/tests/StackExchange.Redis.Tests/ChannelTests.cs
new file mode 100644
index 000000000..3f11d2ef1
--- /dev/null
+++ b/tests/StackExchange.Redis.Tests/ChannelTests.cs
@@ -0,0 +1,153 @@
+﻿using System.Text;
+using Xunit;
+
+namespace StackExchange.Redis.Tests
+{
+    public class ChannelTests
+    {
+        [Fact]
+        public void UseImplicitAutoPattern_OnByDefault()
+        {
+            Assert.True(RedisChannel.UseImplicitAutoPattern);
+        }
+
+        [Theory]
+        [InlineData("abc", true, false)]
+        [InlineData("abc*def", true, true)]
+        [InlineData("abc", false, false)]
+        [InlineData("abc*def", false, false)]
+        public void ValidateAutoPatternModeString(string name, bool useImplicitAutoPattern, bool isPatternBased)
+        {
+            bool oldValue = RedisChannel.UseImplicitAutoPattern;
+            try
+            {
+                RedisChannel.UseImplicitAutoPattern = useImplicitAutoPattern;
+#pragma warning disable CS0618 // we need to test the operator
+                RedisChannel channel = name;
+#pragma warning restore CS0618
+                Assert.Equal(isPatternBased, channel.IsPatternBased);
+            }
+            finally
+            {
+                RedisChannel.UseImplicitAutoPattern = oldValue;
+            }
+        }
+
+        [Theory]
+        [InlineData("abc", RedisChannel.PatternMode.Auto, true, false)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Auto, true, true)]
+        [InlineData("abc", RedisChannel.PatternMode.Literal, true, false)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Literal, true, false)]
+        [InlineData("abc", RedisChannel.PatternMode.Pattern, true, true)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Pattern, true, true)]
+        [InlineData("abc", RedisChannel.PatternMode.Auto, false, false)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Auto, false, true)]
+        [InlineData("abc", RedisChannel.PatternMode.Literal, false, false)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Literal, false, false)]
+        [InlineData("abc", RedisChannel.PatternMode.Pattern, false, true)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Pattern, false, true)]
+        public void ValidateModeSpecifiedIgnoresGlobalSetting(string name, RedisChannel.PatternMode mode, bool useImplicitAutoPattern, bool isPatternBased)
+        {
+            bool oldValue = RedisChannel.UseImplicitAutoPattern;
+            try
+            {
+                RedisChannel.UseImplicitAutoPattern = useImplicitAutoPattern;
+                RedisChannel channel = new(name, mode);
+                Assert.Equal(isPatternBased, channel.IsPatternBased);
+            }
+            finally
+            {
+                RedisChannel.UseImplicitAutoPattern = oldValue;
+            }
+        }
+
+        [Theory]
+        [InlineData("abc", true, false)]
+        [InlineData("abc*def", true, true)]
+        [InlineData("abc", false, false)]
+        [InlineData("abc*def", false, false)]
+        public void ValidateAutoPatternModeBytes(string name, bool useImplicitAutoPattern, bool isPatternBased)
+        {
+            var bytes = Encoding.UTF8.GetBytes(name);
+            bool oldValue = RedisChannel.UseImplicitAutoPattern;
+            try
+            {
+                RedisChannel.UseImplicitAutoPattern = useImplicitAutoPattern;
+#pragma warning disable CS0618 // we need to test the operator
+                RedisChannel channel = bytes;
+#pragma warning restore CS0618
+                Assert.Equal(isPatternBased, channel.IsPatternBased);
+            }
+            finally
+            {
+                RedisChannel.UseImplicitAutoPattern = oldValue;
+            }
+        }
+
+        [Theory]
+        [InlineData("abc", RedisChannel.PatternMode.Auto, true, false)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Auto, true, true)]
+        [InlineData("abc", RedisChannel.PatternMode.Literal, true, false)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Literal, true, false)]
+        [InlineData("abc", RedisChannel.PatternMode.Pattern, true, true)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Pattern, true, true)]
+        [InlineData("abc", RedisChannel.PatternMode.Auto, false, false)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Auto, false, true)]
+        [InlineData("abc", RedisChannel.PatternMode.Literal, false, false)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Literal, false, false)]
+        [InlineData("abc", RedisChannel.PatternMode.Pattern, false, true)]
+        [InlineData("abc*def", RedisChannel.PatternMode.Pattern, false, true)]
+        public void ValidateModeSpecifiedIgnoresGlobalSettingBytes(string name, RedisChannel.PatternMode mode, bool useImplicitAutoPattern, bool isPatternBased)
+        {
+            var bytes = Encoding.UTF8.GetBytes(name);
+            bool oldValue = RedisChannel.UseImplicitAutoPattern;
+            try
+            {
+                RedisChannel.UseImplicitAutoPattern = useImplicitAutoPattern;
+                RedisChannel channel = new(bytes, mode);
+                Assert.Equal(isPatternBased, channel.IsPatternBased);
+            }
+            finally
+            {
+                RedisChannel.UseImplicitAutoPattern = oldValue;
+            }
+        }
+
+        [Theory]
+        [InlineData("abc*def", false)]
+        [InlineData("abcdef", false)]
+        [InlineData("abc*def", true)]
+        [InlineData("abcdef", true)]
+        public void ValidateLiteralPatternMode(string name, bool useImplicitAutoPattern)
+        {
+            bool oldValue = RedisChannel.UseImplicitAutoPattern;
+            try
+            {
+                RedisChannel.UseImplicitAutoPattern = useImplicitAutoPattern;
+                RedisChannel channel;
+
+                // literal, string
+                channel = RedisChannel.Literal(name);
+                Assert.False(channel.IsPatternBased);
+
+                // pattern, string
+                channel = RedisChannel.Pattern(name);
+                Assert.True(channel.IsPatternBased);
+
+                var bytes = Encoding.UTF8.GetBytes(name);
+
+                // literal, byte[]
+                channel = RedisChannel.Literal(bytes);
+                Assert.False(channel.IsPatternBased);
+
+                // pattern, byte[]
+                channel = RedisChannel.Pattern(bytes);
+                Assert.True(channel.IsPatternBased);
+            }
+            finally
+            {
+                RedisChannel.UseImplicitAutoPattern = oldValue;
+            }
+        }
+    }
+}
diff --git a/tests/StackExchange.Redis.Tests/ConfigTests.cs b/tests/StackExchange.Redis.Tests/ConfigTests.cs
index c07b32c8a..b11c968cf 100644
--- a/tests/StackExchange.Redis.Tests/ConfigTests.cs
+++ b/tests/StackExchange.Redis.Tests/ConfigTests.cs
@@ -276,7 +276,7 @@ public void ConnectWithSubscribeDisabled()
         Assert.True(servers[0].IsConnected);
         Assert.False(servers[0].IsSubscriberConnected);
 
-        var ex = Assert.Throws<RedisCommandException>(() => conn.GetSubscriber().Subscribe(Me(), (_, _) => GC.KeepAlive(this)));
+        var ex = Assert.Throws<RedisCommandException>(() => conn.GetSubscriber().Subscribe(RedisChannel.Literal(Me()), (_, _) => GC.KeepAlive(this)));
         Assert.Equal("This operation has been disabled in the command-map and cannot be used: SUBSCRIBE", ex.Message);
     }
 
diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue1101Tests.cs b/tests/StackExchange.Redis.Tests/Issues/Issue1101Tests.cs
index 440d18a8e..3f248b480 100644
--- a/tests/StackExchange.Redis.Tests/Issues/Issue1101Tests.cs
+++ b/tests/StackExchange.Redis.Tests/Issues/Issue1101Tests.cs
@@ -28,7 +28,7 @@ public async Task ExecuteWithUnsubscribeViaChannel()
     {
         using var conn = Create(log: Writer);
 
-        RedisChannel name = Me();
+        RedisChannel name = RedisChannel.Literal(Me());
         var pubsub = conn.GetSubscriber();
         AssertCounts(pubsub, name, false, 0, 0);
 
@@ -93,7 +93,7 @@ public async Task ExecuteWithUnsubscribeViaSubscriber()
     {
         using var conn = Create(shared: false, log: Writer);
 
-        RedisChannel name = Me();
+        RedisChannel name = RedisChannel.Literal(Me());
         var pubsub = conn.GetSubscriber();
         AssertCounts(pubsub, name, false, 0, 0);
 
@@ -144,7 +144,7 @@ public async Task ExecuteWithUnsubscribeViaClearAll()
     {
         using var conn = Create(log: Writer);
 
-        RedisChannel name = Me();
+        RedisChannel name = RedisChannel.Literal(Me());
         var pubsub = conn.GetSubscriber();
         AssertCounts(pubsub, name, false, 0, 0);
 
diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs
index 96f7d4c85..b4eff605a 100644
--- a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs
+++ b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs
@@ -568,8 +568,10 @@ public void LockTake()
     [Fact]
     public void Publish()
     {
+#pragma warning disable CS0618
         prefixed.Publish("channel", "message", CommandFlags.None);
         mock.Verify(_ => _.Publish("prefix:channel", "message", CommandFlags.None));
+#pragma warning restore CS0618
     }
 
     [Fact]
diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs
index d5fea204c..2d6b53390 100644
--- a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs
+++ b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs
@@ -528,8 +528,8 @@ public void LockTakeAsync()
         [Fact]
         public void PublishAsync()
         {
-            prefixed.PublishAsync("channel", "message", CommandFlags.None);
-            mock.Verify(_ => _.PublishAsync("prefix:channel", "message", CommandFlags.None));
+            prefixed.PublishAsync(RedisChannel.Literal("channel"), "message", CommandFlags.None);
+            mock.Verify(_ => _.PublishAsync(RedisChannel.Literal("prefix:channel"), "message", CommandFlags.None));
         }
 
         [Fact]
diff --git a/tests/StackExchange.Redis.Tests/PreserveOrderTests.cs b/tests/StackExchange.Redis.Tests/PreserveOrderTests.cs
index b0f7df995..f11cda5c5 100644
--- a/tests/StackExchange.Redis.Tests/PreserveOrderTests.cs
+++ b/tests/StackExchange.Redis.Tests/PreserveOrderTests.cs
@@ -22,7 +22,7 @@ public void Execute()
         var received = new List<int>();
         Log("Subscribing...");
         const int COUNT = 500;
-        sub.Subscribe(channel, (_, message) =>
+        sub.Subscribe(RedisChannel.Literal(channel), (_, message) =>
         {
             lock (received)
             {
@@ -42,7 +42,7 @@ public void Execute()
             // it all goes to the server and back
             for (int i = 0; i < COUNT; i++)
             {
-                sub.Publish(channel, i, CommandFlags.FireAndForget);
+                sub.Publish(RedisChannel.Literal(channel), i, CommandFlags.FireAndForget);
             }
 
             Log("Allowing time for delivery etc...");
diff --git a/tests/StackExchange.Redis.Tests/PubSubCommandTests.cs b/tests/StackExchange.Redis.Tests/PubSubCommandTests.cs
index 809a9968e..dd77dc106 100644
--- a/tests/StackExchange.Redis.Tests/PubSubCommandTests.cs
+++ b/tests/StackExchange.Redis.Tests/PubSubCommandTests.cs
@@ -17,10 +17,12 @@ public void SubscriberCount()
     {
         using var conn = Create();
 
+#pragma warning disable CS0618
         RedisChannel channel = Me() + Guid.NewGuid();
         var server = conn.GetServer(conn.GetEndPoints()[0]);
 
         var channels = server.SubscriptionChannels(Me() + "*");
+#pragma warning restore CS0618
         Assert.DoesNotContain(channel, channels);
 
         _ = server.SubscriptionPatternCount();
@@ -30,7 +32,9 @@ public void SubscriberCount()
         count = server.SubscriptionSubscriberCount(channel);
         Assert.Equal(1, count);
 
+#pragma warning disable CS0618
         channels = server.SubscriptionChannels(Me() + "*");
+#pragma warning restore CS0618
         Assert.Contains(channel, channels);
     }
 
@@ -39,10 +43,14 @@ public async Task SubscriberCountAsync()
     {
         using var conn = Create();
 
+#pragma warning disable CS0618
         RedisChannel channel = Me() + Guid.NewGuid();
+#pragma warning restore CS0618
         var server = conn.GetServer(conn.GetEndPoints()[0]);
 
+#pragma warning disable CS0618
         var channels = await server.SubscriptionChannelsAsync(Me() + "*").WithTimeout(2000);
+#pragma warning restore CS0618
         Assert.DoesNotContain(channel, channels);
 
         _ = await server.SubscriptionPatternCountAsync().WithTimeout(2000);
@@ -52,7 +60,9 @@ public async Task SubscriberCountAsync()
         count = await server.SubscriptionSubscriberCountAsync(channel).WithTimeout(2000);
         Assert.Equal(1, count);
 
+#pragma warning disable CS0618
         channels = await server.SubscriptionChannelsAsync(Me() + "*").WithTimeout(2000);
+#pragma warning restore CS0618
         Assert.Contains(channel, channels);
     }
 }
diff --git a/tests/StackExchange.Redis.Tests/PubSubMultiserverTests.cs b/tests/StackExchange.Redis.Tests/PubSubMultiserverTests.cs
index d3e634a47..dcf706e76 100644
--- a/tests/StackExchange.Redis.Tests/PubSubMultiserverTests.cs
+++ b/tests/StackExchange.Redis.Tests/PubSubMultiserverTests.cs
@@ -18,8 +18,8 @@ public void ChannelSharding()
         using var conn = (Create(channelPrefix: Me()) as ConnectionMultiplexer)!;
 
         var defaultSlot = conn.ServerSelectionStrategy.HashSlot(default(RedisChannel));
-        var slot1 = conn.ServerSelectionStrategy.HashSlot((RedisChannel)"hey");
-        var slot2 = conn.ServerSelectionStrategy.HashSlot((RedisChannel)"hey2");
+        var slot1 = conn.ServerSelectionStrategy.HashSlot(RedisChannel.Literal("hey"));
+        var slot2 = conn.ServerSelectionStrategy.HashSlot(RedisChannel.Literal("hey2"));
 
         Assert.NotEqual(defaultSlot, slot1);
         Assert.NotEqual(ServerSelectionStrategy.NoSlot, slot1);
@@ -34,7 +34,7 @@ public async Task ClusterNodeSubscriptionFailover()
         using var conn = (Create(allowAdmin: true) as ConnectionMultiplexer)!;
 
         var sub = conn.GetSubscriber();
-        var channel = (RedisChannel)Me();
+        var channel = RedisChannel.Literal(Me());
 
         var count = 0;
         Log("Subscribing...");
@@ -108,7 +108,7 @@ public async Task PrimaryReplicaSubscriptionFailover(CommandFlags flags, bool ex
         using var conn = (Create(configuration: config, shared: false, allowAdmin: true) as ConnectionMultiplexer)!;
 
         var sub = conn.GetSubscriber();
-        var channel = (RedisChannel)(Me() + flags.ToString()); // Individual channel per case to not overlap publishers
+        var channel = RedisChannel.Literal(Me() + flags.ToString()); // Individual channel per case to not overlap publishers
 
         var count = 0;
         Log("Subscribing...");
diff --git a/tests/StackExchange.Redis.Tests/PubSubTests.cs b/tests/StackExchange.Redis.Tests/PubSubTests.cs
index 8fd23ffdd..833d888e9 100644
--- a/tests/StackExchange.Redis.Tests/PubSubTests.cs
+++ b/tests/StackExchange.Redis.Tests/PubSubTests.cs
@@ -27,9 +27,11 @@ public async Task ExplicitPublishMode()
         pub.Subscribe(new RedisChannel("*bcd", RedisChannel.PatternMode.Literal), (x, y) => Interlocked.Increment(ref a));
         pub.Subscribe(new RedisChannel("a*cd", RedisChannel.PatternMode.Pattern), (x, y) => Interlocked.Increment(ref b));
         pub.Subscribe(new RedisChannel("ab*d", RedisChannel.PatternMode.Auto), (x, y) => Interlocked.Increment(ref c));
+#pragma warning disable CS0618
         pub.Subscribe("abc*", (x, y) => Interlocked.Increment(ref d));
 
         pub.Publish("abcd", "efg");
+#pragma warning restore CS0618
         await UntilConditionAsync(TimeSpan.FromSeconds(10),
             () => Thread.VolatileRead(ref b) == 1
                && Thread.VolatileRead(ref c) == 1
@@ -39,7 +41,9 @@ await UntilConditionAsync(TimeSpan.FromSeconds(10),
         Assert.Equal(1, Thread.VolatileRead(ref c));
         Assert.Equal(1, Thread.VolatileRead(ref d));
 
+#pragma warning disable CS0618
         pub.Publish("*bcd", "efg");
+#pragma warning restore CS0618
         await UntilConditionAsync(TimeSpan.FromSeconds(10), () => Thread.VolatileRead(ref a) == 1);
         Assert.Equal(1, Thread.VolatileRead(ref a));
     }
@@ -77,15 +81,19 @@ public async Task TestBasicPubSub(string channelPrefix, bool wildCard, string br
             }
         }
         , handler2 = (_, __) => Interlocked.Increment(ref secondHandler);
+#pragma warning disable CS0618
         sub.Subscribe(subChannel, handler1);
         sub.Subscribe(subChannel, handler2);
+#pragma warning restore CS0618
 
         lock (received)
         {
             Assert.Empty(received);
         }
         Assert.Equal(0, Thread.VolatileRead(ref secondHandler));
+#pragma warning disable CS0618
         var count = sub.Publish(pubChannel, "def");
+#pragma warning restore CS0618
 
         await PingAsync(pub, sub, 3).ForAwait();
 
@@ -99,8 +107,10 @@ public async Task TestBasicPubSub(string channelPrefix, bool wildCard, string br
         Assert.Equal(1, Thread.VolatileRead(ref secondHandler));
 
         // unsubscribe from first; should still see second
+#pragma warning disable CS0618
         sub.Unsubscribe(subChannel, handler1);
         count = sub.Publish(pubChannel, "ghi");
+#pragma warning restore CS0618
         await PingAsync(pub, sub).ForAwait();
         lock (received)
         {
@@ -115,8 +125,10 @@ public async Task TestBasicPubSub(string channelPrefix, bool wildCard, string br
         Assert.Equal(1, count);
 
         // unsubscribe from second; should see nothing this time
+#pragma warning disable CS0618
         sub.Unsubscribe(subChannel, handler2);
         count = sub.Publish(pubChannel, "ghi");
+#pragma warning restore CS0618
         await PingAsync(pub, sub).ForAwait();
         lock (received)
         {
@@ -137,7 +149,7 @@ public async Task TestBasicPubSubFireAndForget()
         var pub = GetAnyPrimary(conn);
         var sub = conn.GetSubscriber();
 
-        RedisChannel key = Me() + Guid.NewGuid();
+        RedisChannel key = RedisChannel.Literal(Me() + Guid.NewGuid());
         HashSet<string?> received = new();
         int secondHandler = 0;
         await PingAsync(pub, sub).ForAwait();
@@ -210,7 +222,9 @@ public async Task TestPatternPubSub()
 
         HashSet<string?> received = new();
         int secondHandler = 0;
+#pragma warning disable CS0618
         sub.Subscribe("a*c", (channel, payload) =>
+#pragma warning restore CS0618
         {
             lock (received)
             {
@@ -221,7 +235,9 @@ public async Task TestPatternPubSub()
             }
         });
 
+#pragma warning disable CS0618
         sub.Subscribe("a*c", (_, __) => Interlocked.Increment(ref secondHandler));
+#pragma warning restore CS0618
         lock (received)
         {
             Assert.Empty(received);
@@ -229,7 +245,7 @@ public async Task TestPatternPubSub()
         Assert.Equal(0, Thread.VolatileRead(ref secondHandler));
 
         await PingAsync(pub, sub).ForAwait();
-        var count = sub.Publish("abc", "def");
+        var count = sub.Publish(RedisChannel.Literal("abc"), "def");
         await PingAsync(pub, sub).ForAwait();
 
         await UntilConditionAsync(TimeSpan.FromSeconds(5), () => received.Count == 1);
@@ -242,8 +258,10 @@ public async Task TestPatternPubSub()
         await UntilConditionAsync(TimeSpan.FromSeconds(2), () => Thread.VolatileRead(ref secondHandler) == 1);
         Assert.Equal(1, Thread.VolatileRead(ref secondHandler));
 
+#pragma warning disable CS0618
         sub.Unsubscribe("a*c");
         count = sub.Publish("abc", "ghi");
+#pragma warning restore CS0618
 
         await PingAsync(pub, sub).ForAwait();
 
@@ -259,7 +277,9 @@ public void TestPublishWithNoSubscribers()
         using var conn = Create();
 
         var sub = conn.GetSubscriber();
+#pragma warning disable CS0618
         Assert.Equal(0, sub.Publish(Me() + "channel", "message"));
+#pragma warning restore CS0618
     }
 
     [FactLongRunning]
@@ -289,14 +309,18 @@ private void TestMassivePublish(ISubscriber sub, string channel, string caption)
         var withFAF = Stopwatch.StartNew();
         for (int i = 0; i < loop; i++)
         {
+#pragma warning disable CS0618
             sub.Publish(channel, "bar", CommandFlags.FireAndForget);
+#pragma warning restore CS0618
         }
         withFAF.Stop();
 
         var withAsync = Stopwatch.StartNew();
         for (int i = 0; i < loop; i++)
         {
+#pragma warning disable CS0618
             tasks[i] = sub.PublishAsync(channel, "bar");
+#pragma warning restore CS0618
         }
         sub.WaitAll(tasks);
         withAsync.Stop();
@@ -314,7 +338,7 @@ public async Task SubscribeAsyncEnumerable()
         using var conn = Create(syncTimeout: 20000, shared: false, log: Writer);
 
         var sub = conn.GetSubscriber();
-        RedisChannel channel = Me();
+        RedisChannel channel = RedisChannel.Literal(Me());
 
         const int TO_SEND = 5;
         var gotall = new TaskCompletionSource<int>();
@@ -348,7 +372,7 @@ public async Task PubSubGetAllAnyOrder()
         using var sonn = Create(syncTimeout: 20000, shared: false, log: Writer);
 
         var sub = sonn.GetSubscriber();
-        RedisChannel channel = Me();
+        RedisChannel channel = RedisChannel.Literal(Me());
         const int count = 1000;
         var syncLock = new object();
 
@@ -396,7 +420,7 @@ public async Task PubSubGetAllCorrectOrder()
         using (var conn = Create(configuration: TestConfig.Current.RemoteServerAndPort, syncTimeout: 20000, log: Writer))
         {
             var sub = conn.GetSubscriber();
-            RedisChannel channel = Me();
+            RedisChannel channel = RedisChannel.Literal(Me());
             const int count = 250;
             var syncLock = new object();
 
@@ -469,7 +493,7 @@ public async Task PubSubGetAllCorrectOrder_OnMessage_Sync()
         using (var conn = Create(configuration: TestConfig.Current.RemoteServerAndPort, syncTimeout: 20000, log: Writer))
         {
             var sub = conn.GetSubscriber();
-            RedisChannel channel = Me();
+            RedisChannel channel = RedisChannel.Literal(Me());
             const int count = 1000;
             var syncLock = new object();
 
@@ -538,7 +562,7 @@ public async Task PubSubGetAllCorrectOrder_OnMessage_Async()
         using (var conn = Create(configuration: TestConfig.Current.RemoteServerAndPort, syncTimeout: 20000, log: Writer))
         {
             var sub = conn.GetSubscriber();
-            RedisChannel channel = Me();
+            RedisChannel channel = RedisChannel.Literal(Me());
             const int count = 1000;
             var syncLock = new object();
 
@@ -616,8 +640,10 @@ public async Task TestPublishWithSubscribers()
         var channel = Me();
         var listenA = connA.GetSubscriber();
         var listenB = connB.GetSubscriber();
+#pragma warning disable CS0618
         var t1 = listenA.SubscribeAsync(channel, delegate { });
         var t2 = listenB.SubscribeAsync(channel, delegate { });
+#pragma warning restore CS0618
 
         await Task.WhenAll(t1, t2).ForAwait();
 
@@ -625,7 +651,9 @@ public async Task TestPublishWithSubscribers()
         await listenA.PingAsync();
         await listenB.PingAsync();
 
+#pragma warning disable CS0618
         var pub = connPub.GetSubscriber().PublishAsync(channel, "message");
+#pragma warning restore CS0618
         Assert.Equal(2, await pub); // delivery count
     }
 
@@ -636,7 +664,7 @@ public async Task TestMultipleSubscribersGetMessage()
         using var connB = Create(shared: false, log: Writer);
         using var connPub = Create();
 
-        var channel = Me();
+        var channel = RedisChannel.Literal(Me());
         var listenA = connA.GetSubscriber();
         var listenB = connB.GetSubscriber();
         connPub.GetDatabase().Ping();
@@ -668,16 +696,20 @@ public async Task Issue38()
         int count = 0;
         var prefix = Me();
         void handler(RedisChannel _, RedisValue __) => Interlocked.Increment(ref count);
+#pragma warning disable CS0618
         var a0 = sub.SubscribeAsync(prefix + "foo", handler);
         var a1 = sub.SubscribeAsync(prefix + "bar", handler);
         var b0 = sub.SubscribeAsync(prefix + "f*o", handler);
         var b1 = sub.SubscribeAsync(prefix + "b*r", handler);
+#pragma warning restore CS0618
         await Task.WhenAll(a0, a1, b0, b1).ForAwait();
 
+#pragma warning disable CS0618
         var c = sub.PublishAsync(prefix + "foo", "foo");
         var d = sub.PublishAsync(prefix + "f@o", "f@o");
         var e = sub.PublishAsync(prefix + "bar", "bar");
         var f = sub.PublishAsync(prefix + "b@r", "b@r");
+#pragma warning restore CS0618
         await Task.WhenAll(c, d, e, f).ForAwait();
 
         long total = c.Result + d.Result + e.Result + f.Result;
@@ -702,18 +734,22 @@ public async Task TestPartialSubscriberGetMessage()
         var listenB = connB.GetSubscriber();
         var pub = connPub.GetSubscriber();
         var prefix = Me();
+#pragma warning disable CS0618
         var tA = listenA.SubscribeAsync(prefix + "channel", (s, msg) => { if (s == prefix + "channel" && msg == "message") Interlocked.Increment(ref gotA); });
         var tB = listenB.SubscribeAsync(prefix + "chann*", (s, msg) => { if (s == prefix + "channel" && msg == "message") Interlocked.Increment(ref gotB); });
         await Task.WhenAll(tA, tB).ForAwait();
         Assert.Equal(2, pub.Publish(prefix + "channel", "message"));
+#pragma warning restore CS0618
         await AllowReasonableTimeToPublishAndProcess().ForAwait();
         Assert.Equal(1, Interlocked.CompareExchange(ref gotA, 0, 0));
         Assert.Equal(1, Interlocked.CompareExchange(ref gotB, 0, 0));
 
         // and unsubscibe...
+#pragma warning disable CS0618
         tB = listenB.UnsubscribeAsync(prefix + "chann*", null);
         await tB;
         Assert.Equal(1, pub.Publish(prefix + "channel", "message"));
+#pragma warning restore CS0618
         await AllowReasonableTimeToPublishAndProcess().ForAwait();
         Assert.Equal(2, Interlocked.CompareExchange(ref gotA, 0, 0));
         Assert.Equal(1, Interlocked.CompareExchange(ref gotB, 0, 0));
@@ -729,6 +765,7 @@ public async Task TestSubscribeUnsubscribeAndSubscribeAgain()
         var pub = connPub.GetSubscriber();
         var sub = connSub.GetSubscriber();
         int x = 0, y = 0;
+#pragma warning disable CS0618
         var t1 = sub.SubscribeAsync(prefix + "abc", delegate { Interlocked.Increment(ref x); });
         var t2 = sub.SubscribeAsync(prefix + "ab*", delegate { Interlocked.Increment(ref y); });
         await Task.WhenAll(t1, t2).ForAwait();
@@ -746,6 +783,7 @@ public async Task TestSubscribeUnsubscribeAndSubscribeAgain()
         t2 = sub.SubscribeAsync(prefix + "ab*", delegate { Interlocked.Increment(ref y); });
         await Task.WhenAll(t1, t2).ForAwait();
         pub.Publish(prefix + "abc", "");
+#pragma warning restore CS0618
         await AllowReasonableTimeToPublishAndProcess().ForAwait();
         Assert.Equal(2, Volatile.Read(ref x));
         Assert.Equal(2, Volatile.Read(ref y));
@@ -776,7 +814,7 @@ public async Task AzureRedisEventsAutomaticSubscribe()
             };
 
             var pubSub = connection.GetSubscriber();
-            await pubSub.PublishAsync("AzureRedisEvents", "HI");
+            await pubSub.PublishAsync(RedisChannel.Literal("AzureRedisEvents"), "HI");
             await Task.Delay(100);
 
             Assert.True(didUpdate);
diff --git a/tests/StackExchange.Redis.Tests/SentinelFailoverTests.cs b/tests/StackExchange.Redis.Tests/SentinelFailoverTests.cs
index 0afdf03ec..1e4d8c28e 100644
--- a/tests/StackExchange.Redis.Tests/SentinelFailoverTests.cs
+++ b/tests/StackExchange.Redis.Tests/SentinelFailoverTests.cs
@@ -21,7 +21,9 @@ public async Task ManagedPrimaryConnectionEndToEndWithFailoverTest()
         conn.ConfigurationChanged += (s, e) => Log($"Configuration changed: {e.EndPoint}");
 
         var sub = conn.GetSubscriber();
+#pragma warning disable CS0618
         sub.Subscribe("*", (channel, message) => Log($"Sub: {channel}, message:{message}"));
+#pragma warning restore CS0618
 
         var db = conn.GetDatabase();
         await db.PingAsync();
diff --git a/tests/StackExchange.Redis.Tests/TestBase.cs b/tests/StackExchange.Redis.Tests/TestBase.cs
index 6738bf490..4ba21d4f5 100644
--- a/tests/StackExchange.Redis.Tests/TestBase.cs
+++ b/tests/StackExchange.Redis.Tests/TestBase.cs
@@ -377,7 +377,7 @@ public static ConnectionMultiplexer CreateDefault(
                 syncTimeout = int.MaxValue;
             }
 
-            if (channelPrefix != null) config.ChannelPrefix = channelPrefix;
+            if (channelPrefix != null) config.ChannelPrefix = RedisChannel.Literal(channelPrefix);
             if (tieBreaker != null) config.TieBreaker = tieBreaker;
             if (password != null) config.Password = string.IsNullOrEmpty(password) ? null : password;
             if (clientName != null) config.ClientName = clientName;