FreeRedis: a Redis client for .NET that covers cluster, sentinel and RESP3
🦄 FreeRedis is .NET40+ redis client. supports cluster, sentinel, master-slave, pub-sub, lua, pipeline, transaction, streams, redisearch, client-side-caching, and pooling.
At a glance
- What is it?
- FreeRedis is a .NET Redis client whose method names follow redis-cli, with support for cluster, sentinel, master-slave, pub-sub, Lua, pipelines, transactions, streams, RediSearch and client-side caching. It suits a .NET team that needs the newer Redis features without a rewrite, and it targets .NET Core 2.1 and .NET Framework 4.0 upward.
- Who is it for?
- Adopt FreeRedis when your .NET service needs Redis features that the older maintained clients have not caught up with, particularly client-side caching and RESP3, because both are supported here and the client ships a pooling implementation and a DistributedCache adapter. Do not adopt it without reading the release dates, because the newest tag is v1.0.1 from 2022 and the last push is from 2026, so the working tree and the published package are different things.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 25 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Method names that match redis-cli
FreeRedis is a Redis client based on .NET that supports .NET Core 2.1 and up, .NET Framework 4.0 and up, Xamarin, and AOT. The primary language is C#, the licence is MIT, and it is published on NuGet.
The design principle is stated first in the feature list: RedisClient keeps all method names consistent with redis-cli. That is a bigger deal than it sounds, because it means a person who knows the Redis command line can predict the .NET method name without looking anything up. Get is Get, MSet is MSet, and the multi-argument forms take the same argument order as the command.
The quick start shows how little ceremony there is:
public static RedisClient cli = new RedisClient("127.0.0.1:6379,password=123,defaultDatabase=13");
cli.Serialize = obj => JsonConvert.SerializeObject(obj);
cli.Deserialize = (json, type) => JsonConvert.DeserializeObject(json, type);
cli.Notice += (s, e) => Console.WriteLine(e.Log); //print command log
cli.Set("key1", "value1");
cli.MSet("key1", "value1", "key2", "value2");
string value1 = cli.Get("key1");
string[] vals = cli.MGet("key1", "key2");The connection string is a single comma separated string carrying the host, the password and the default database, and the client is constructed from it with no options object. Serialization is a pair of delegates you assign rather than a configuration setting, which is a deliberate choice to leave the JSON library to the caller, since the surrounding application will already have one.
The Notice event is the third piece and it is the diagnostics hook, printing the command log as each command runs.
The data types listed after the example cover strings, hashes, lists, sets, sorted sets, bitmaps, hyperloglogs, geo, streams and BloomFilter, and the client also provides a FreeRedis.DistributedCache that plugs into the ASP.NET Core IDistributedCache interface.
The connection string options, and what each one trades away
The options table is the part of the README most worth reading slowly, because several of the defaults are opinionated.
The protocol defaults to RESP2, and the note says that if you use RESP3 you need a Redis 6.0 environment. The user option is empty by default and requires a Redis server 6.0, and password is the server password. defaultDatabase is 0.
The pool is where the interesting defaults sit. max poolsize is 100, min poolsize is 5, and idleTimeout is 20000 milliseconds, described as the idle time of elements in the connection pool and noted as suitable for connecting to a remote Redis server. The three timeouts, connectTimeout, receiveTimeout and sendTimeout, all default to 10000 milliseconds.
Then the ones that change behaviour rather than tuning it. The retry option defaults to 0 and is the number of times a protocol error is retried, so by default a protocol error is not retried at all. ssl defaults to false and enables encrypted transmission. The name option is empty and is the connection name, visible with the client list command.
The prefix option is the one with the widest blast radius. All methods will have this prefix, and the example given is cli.Set(prefix + "key", 111). So a prefixed client prepends the prefix to every key it touches, and the caller still writes the prefix on a raw Set call, which tells you the prefix is a convenience for the built-in methods rather than a transparent key transformation.
exitAutoDisposePool defaults to true and means the connection pool is disposed automatically on AppDomain.CurrentDomain.ProcessExit and on Console.CancelKeyPress. That default is convenient in a console app and wrong in a long-running service that starts and stops workers, which is worth knowing before you rely on it.
The last two are narrow. subscribeReadbytes, false by default, subscribes to read byte counts, which is a per-connection metric. And the IPv6 note shows a scoped address format, fe80::b164:55b3:4b4f:7ce6%15 with the port 6379.
Master-slave, sentinel and cluster, and what each topology changes
The three deployment topologies are shown as variations on the same constructor, and the differences are about where writes go and where reads come from.
Master-slave is a list of connection strings in one constructor call:
public static RedisClient cli = new RedisClient(
"127.0.0.1:6379,password=123,defaultDatabase=13",
"127.0.0.1:6380,password=123,defaultDatabase=13",
"127.0.0.1:6381,password=123,defaultDatabase=13"
);
var value = cli.Get("key1");The note says writes go to 127.0.0.1:6379 and reads come randomly from port 6380 or 6381. So the first address in the list is the writer and the rest are readers, which is a convention you have to know and is not obvious from the signature.
Sentinel takes a master name, a password, the sentinel addresses and a flag:
public static RedisClient cli = new RedisClient(
"mymaster,password=123",
new [] { "192.169.1.10:26379", "192.169.1.11:26379", "192.169.1.12:26379" },
true //This variable indicates whether to use the read-write separation mode.
);That last boolean is the read-write separation mode, and it is the interesting part, because Sentinel exists precisely to handle a master that changes. The first argument names the master group, mymaster, which is how the sentinels know which primary you mean.
Cluster is a set of connection string builders:
public static RedisClient cli = new RedisClient(
new ConnectionStringBuilder[] { "192.168.0.2:7001", "192.168.0.2:7002", "192.168.0.2:7003" }
);The README's example describes a cluster with three master nodes on 7001 to 7003 and three slave nodes on 7004 to 7006, and the code lists the three masters. Cluster support requires redis-server 3.2 and above, and the Scan example is explicitly noted as supporting cluster mode, which is a meaningful detail because a naive scan against a clustered keyspace is a common source of partial results.
Client-side caching, and the server version it depends on
Client-side caching is the feature that distinguishes this client from the older .NET Redis options, and it requires redis-server 6.0 and above.
The API is a configuration object with three properties. Capacity is the client cache capacity. KeyFilter is the filtering rule that specifies which keys can be cached locally. And CheckExpired checks for long-term unused cache.
cli.UseClientSideCaching(new ClientSideCachingOptions
{
//Client cache capacity
Capacity = 3,
//Filtering rules, which specify which keys can be cached locally
KeyFilter = key => key.StartsWith("Interceptor"),
//Check long-term unused cache
CheckExpired = (key, dt) => DateTime.Now.Subtract(dt) > TimeSpan.FromSeconds(2)
});The design puts the eviction policy in the hands of the caller rather than in the library. The client maintains a local cache, but the KeyFilter and CheckExpired delegates decide what stays in it, which means you can cache only the keys your code actually reads repeatedly, and you can expire entries on your own terms rather than on a fixed TTL.
The example makes that concrete. The capacity is 3, which is small because the filter only admits keys starting with Interceptor, so in practice the filter is the constraint and the capacity is a ceiling. The expiry check compares the current time against a stored timestamp and drops anything unused for more than two seconds.
Client-side caching in Redis 6.0 works through the server tracking which keys a client is reading, and the client then serves repeats from memory. The value of it is real for read-heavy workloads where the same key is fetched constantly, and the value of the client-side API here is that the policy is yours.
The repository has a matching example project, examples/console_net8_client_side_caching, and a combined cluster plus client-side caching example, so you can see the two features used together.
Pipelines, transactions and the other grouped operations
The remaining feature groups follow a consistent pattern, where a using block opens a grouped operation and the result comes back when the block ends.
A pipeline batches commands:
using (var pipe = cli.StartPipe())
{
pipe.IncrBy("key1", 10);
pipe.Set("key2", Null);
pipe.Get("key1");
object[] ret = pipe.EndPipe();
Console.WriteLine(ret[0] + ", " + ret[2]);
}And a transaction is the same shape with a different method name, Multi for the transaction and Exec for the result:
using (var tran = cli.Multi())
{
tran.IncrBy("key1", 10);
tran.Set("key2", Null);
tran.Get("key1");
object[] ret = tran.Exec();
Console.WriteLine(ret[0] + ", " + ret[2]);
}The result indexing is worth noticing. ret[0] and ret[2] are the results of the IncrBy and the Get, while the Set returns Null, and the same indexing applies to both, so the two blocks are readable side by side.
Switching database is the same pattern with GetDatabase, which returns a scoped client:
using (var db = cli.GetDatabase(10))
{
db.Set("key1", 10);
var val1 = db.Get("key1");
}Pub-sub has three shapes, for a channel, for a list and for a stream, and each is a using block with a callback, and the comment says to wait for Dispose. Subscribe takes a channel and a handler, SubscribeList takes a list key, and SubscribeStream takes a stream key and a handler that receives a dictionary. The stream example also shows the pending query:
// NoAck xpending
cli.XPending("stream_key", "FreeRedis__group", "-", "+", 10);The group name FreeRedis__group is passed explicitly, and the range is the full span from the lowest to the highest pending entry, limited to ten results.
Lua scripting is exposed through Eval, which takes the script, the keys and the arguments separately, and the third example calls redis.call from inside the script. DelayQueue is the remaining grouped structure, and it takes a name and a set of enqueue calls with a delay.
Editorial conclusion
Adopt FreeRedis when your .NET service needs Redis features that the older maintained clients have not caught up with, particularly client-side caching and RESP3, because both are supported here and the client ships a pooling implementation and a DistributedCache adapter. Do not adopt it without reading the release dates, because the newest tag is v1.0.1 from 2022 and the last push is from 2026, so the working tree and the published package are different things. Before you rely on it, check the server version each feature needs, since client-side caching requires Redis 6.0, streams require 5.0, and cluster and geo require 3.2, and decide whether you want the 2022 package or a build from the current branch.
Frequently asked questions
What is FreeRedis and what does it support?
It is a Redis client based on .NET, supporting .NET Core 2.1 and up, .NET Framework 4.0 and up, Xamarin and AOT. It supports Redis Cluster from redis-server 3.2, Sentinel, master-slave, pub-sub, Lua scripting, pipeline, transaction, delay queue, RediSearch, geo types from 3.2, streams from 5.0, client-side caching from 6.0 and the RESP3 protocol.
How do I connect FreeRedis to a Redis Sentinel or Cluster setup?
For Sentinel you pass the master group name with the password, the array of sentinel addresses, and a boolean indicating read-write separation mode. For Cluster you pass an array of ConnectionStringBuilder entries for the cluster nodes. Master-slave takes the writer address first in the list and the readers after it.
How does client-side caching work in FreeRedis?
You call UseClientSideCaching with a ClientSideCachingOptions object holding the cache Capacity, a KeyFilter delegate specifying which keys can be cached locally, and a CheckExpired delegate for long-term unused cache entries. The feature requires redis-server 6.0 and above, and the policy stays in your code rather than in the library.
Which Redis types and features does FreeRedis support?
The client supports strings, hashes, lists, sets, sorted sets, bitmaps, hyperloglogs, geo, streams and BloomFilter. The feature list also covers Lua scripting, pipeline, transaction, delay queue, RediSearch, client-side caching and the RESP3 protocol, and it provides a FreeRedis.DistributedCache implementation for the IDistributedCache interface.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/2881099-freeredis)Was this useful?