FreeRedis
RedisClient.cs
using FreeRedis.Internal;
using FreeRedis.Internal.ObjectPool;
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Collections;
using System.Runtime.InteropServices;
using System.Reflection;
namespace FreeRedis
{
public partial clast RedisClient : IDisposable
{
internal protected BaseAdapter Adapter { get; }
internal protected string Prefix { get; }
public List Interceptors { get; } = new List();
public event EventHandler Notice;
public event EventHandler Connected;
public event EventHandler Unavailable;
protected RedisClient(BaseAdapter adapter)
{
Adapter = adapter;
}
///
/// Pooling RedisClient
///
public RedisClient(ConnectionStringBuilder connectionString, params ConnectionStringBuilder[] slaveConnectionStrings)
{
Adapter = new PoolingAdapter(this, connectionString, slaveConnectionStrings);
Prefix = connectionString.Prefix;
}
///
/// Cluster RedisClient
///
public RedisClient(ConnectionStringBuilder[] clusterConnectionStrings)
{
Adapter = new ClusterAdapter(this, clusterConnectionStrings);
Prefix = clusterConnectionStrings[0].Prefix;
}
///
/// Norman RedisClient
///
public RedisClient(ConnectionStringBuilder[] connectionStrings, Func redirectRule)
{
Adapter = new NormanAdapter(this, connectionStrings, redirectRule);
Prefix = connectionStrings[0].Prefix;
}
///
/// Sentinel RedisClient
///
public RedisClient(ConnectionStringBuilder sentinelConnectionString, string[] sentinels, bool rw_splitting)
{
Adapter = new SentinelAdapter(this, sentinelConnectionString, sentinels, rw_splitting);
Prefix = sentinelConnectionString.Prefix;
}
///
/// Single inside RedisClient
///
protected internal RedisClient(RedisClient topOwner, string host, bool ssl, TimeSpan connectTimeout, TimeSpan receiveTimeout, TimeSpan sendTimeout, Action connected)
{
Adapter = new SingleInsideAdapter(topOwner ?? this, this, host, ssl, connectTimeout, receiveTimeout, sendTimeout, connected);
Prefix = topOwner.Prefix;
}
~RedisClient() => this.Dispose();
int _disposeCounter;
public void Dispose()
{
if (Interlocked.Increment(ref _disposeCounter) != 1) return;
Adapter.Dispose();
_pubsubPriv?.Dispose();
}
protected void CheckUseTypeOrThrow(params UseType[] useTypes)
{
if (useTypes?.Contains(Adapter.UseType) == true) return;
throw new RedisClientException($"Method cannot be used in {Adapter.UseType} mode.");
}
public object Call(CommandPacket cmd) => Adapter.AdapterCall(cmd, rt => rt.ThrowOrValue());
protected TValue Call(CommandPacket cmd, Func parse) => Adapter.AdapterCall(cmd, parse);
internal protected virtual T LogCall(CommandPacket cmd, Func func)
{
cmd.Prefix(Prefix);
var isnotice = this.Notice != null;
if (isnotice == false && this.Interceptors.Any() == false) return func();
Exception exception = null;
T ret = default(T);
var isaopval = false;
IInterceptor[] aops = new IInterceptor[this.Interceptors.Count + (isnotice ? 1 : 0)];
Stopwatch[] aopsws = new Stopwatch[aops.Length];
for (var idx = 0; idx < aops.Length; idx++)
{
aopsws[idx] = new Stopwatch();
aopsws[idx].Start();
aops[idx] = isnotice && idx == aops.Length - 1 ? new NoticeCallInterceptor(this) : this.Interceptors[idx]?.Invoke();
var args = new InterceptorBeforeEventArgs(this, cmd, typeof(T));
aops[idx].Before(args);
if (args.ValueIsChanged && args.Value is T argsValue)
{
isaopval = true;
ret = argsValue;
}
}
try
{
if (isaopval == false) ret = func();
return ret;
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
for (var idx = 0; idx < aops.Length; idx++)
{
aopsws[idx].Stop();
var args = new InterceptorAfterEventArgs(this, cmd, typeof(T), ret, exception, aopsws[idx].ElapsedMilliseconds);
aops[idx].After(args);
}
}
}
internal bool OnNotice(RedisClient cli, NoticeEventArgs e)
{
var topOwner = Adapter?.TopOwner ?? cli;
if (topOwner?.Notice == null) return false;
topOwner.Notice(topOwner, e);
return true;
}
internal bool OnConnected(RedisClient cli, ConnectedEventArgs e)
{
var topOwner = Adapter?.TopOwner ?? cli;
if (topOwner?.Connected == null) return false;
topOwner.Connected(topOwner, e);
return true;
}
internal bool OnUnavailable(RedisClient cli, UnavailableEventArgs e)
{
var topOwner = Adapter?.TopOwner ?? cli;
if (topOwner?.Unavailable == null) return false;
topOwner.Unavailable(topOwner, e);
return true;
}
#region 序列化写入,反序列化
public Func Serialize;
public Func Deserialize;
public Func DeserializeRaw;
internal object SerializeRedisValue(object value)
{
switch (value)
{
case null: return null;
case string _:
case byte[] _: return value;
case bool b: return b ? "1" : "0";
case char c: return value;
case decimal _:
case double _:
case float _:
case int _:
case long _:
case sbyte _:
case short _:
case uint _:
case ulong _:
case ushort _: return value.ToInvariantCultureToString();
case DateTime time: return time.ToString("yyyy-MM-ddTHH:mm:sszzzz", System.Globalization.DateTimeFormatInfo.InvariantInfo);
case DateTimeOffset _: return value.ToString();
case TimeSpan span: return span.Ticks;
case Guid _: return value.ToString();
default:
if (Adapter.TopOwner.Serialize != null) return Adapter.TopOwner.Serialize(value);
return value.ConvertTo();
}
}
internal T DeserializeRedisValue(byte[] valueRaw, Encoding encoding)
{
if (valueRaw == null) return default(T);
var type = typeof(T);
var typename = type.ToString().TrimEnd(']');
if (typename == "System.Byte[") return (T)Convert.ChangeType(valueRaw, type);
if (typename == "System.String") return (T)Convert.ChangeType(encoding.GetString(valueRaw), type);
if (typename == "System.Boolean[") return (T)Convert.ChangeType(valueRaw.Select(a => a == 49).ToArray(), type);
if (valueRaw.Length == 0) return default(T);
string valueStr = null;
if (type.IsValueType)
{
valueStr = encoding.GetString(valueRaw);
bool isNullable = typename.StartsWith("System.Nullable`1[");
var basename = isNullable ? typename.Substring(18) : typename;
bool isElse = false;
object obj = null;
switch (basename)
{
case "System.Boolean":
if (valueStr == "1") obj = true;
else if (valueStr == "0") obj = false;
break;
case "System.Byte":
if (byte.TryParse(valueStr, out var trybyte)) obj = trybyte;
break;
case "System.Char":
if (valueStr.Length > 0) obj = valueStr[0];
break;
case "System.Decimal":
if (Decimal.TryParse(valueStr, out var trydec)) obj = trydec;
break;
case "System.Double":
if (Double.TryParse(valueStr, out var trydb)) obj = trydb;
break;
case "System.Single":
if (Single.TryParse(valueStr, out var trysg)) obj = trysg;
break;
case "System.Int32":
if (Int32.TryParse(valueStr, out var tryint32)) obj = tryint32;
break;
case "System.Int64":
if (Int64.TryParse(valueStr, out var tryint64)) obj = tryint64;
break;
case "System.SByte":
if (SByte.TryParse(valueStr, out var trysb)) obj = trysb;
break;
case "System.Int16":
if (Int16.TryParse(valueStr, out var tryint16)) obj = tryint16;
break;
case "System.UInt32":
if (UInt32.TryParse(valueStr, out var tryuint32)) obj = tryuint32;
break;
case "System.UInt64":
if (UInt64.TryParse(valueStr, out var tryuint64)) obj = tryuint64;
break;
case "System.UInt16":
if (UInt16.TryParse(valueStr, out var tryuint16)) obj = tryuint16;
break;
case "System.DateTime":
if (DateTime.TryParse(valueStr, out var trydt)) obj = trydt;
break;
case "System.DateTimeOffset":
if (DateTimeOffset.TryParse(valueStr, out var trydtos)) obj = trydtos;
break;
case "System.TimeSpan":
if (Int64.TryParse(valueStr, out tryint64)) obj = new TimeSpan(tryint64);
break;
case "System.Guid":
if (Guid.TryParse(valueStr, out var tryguid)) obj = tryguid;
break;
default:
isElse = true;
break;
}
if (isElse == false)
{
if (obj == null) return default(T);
return (T)obj;
}
}
if (Adapter.TopOwner.DeserializeRaw != null) return (T)Adapter.TopOwner.DeserializeRaw(valueRaw, typeof(T));
if (valueStr == null) valueStr = encoding.GetString(valueRaw);
if (Adapter.TopOwner.Deserialize != null) return (T)Adapter.TopOwner.Deserialize(valueStr, typeof(T));
return valueStr.ConvertTo();
}
#endregion
}
public clast RedisClientException : Exception
{
public RedisClientException(string message) : base(message) { }
}
///
/// redis version >=6.2: Added the GT and LT options.
///
public enum ZAddThan { gt, lt }
public enum BitOpOperation { and, or, xor, not }
public enum ClusterSetSlotType { importing, migrating, stable, node }
public enum ClusterResetType { hard, soft }
public enum ClusterFailOverType { force, takeover }
public enum ClientUnBlockType { timeout, error }
public enum ClientReplyType { on, off, skip }
public enum ClientType { normal, master, slave, pubsub }
public enum Collation { asc, desc }
public enum Confirm { yes, no }
public enum GeoUnit { m, km, mi, ft }
public enum InsertDirection { before, after }
public enum KeyType { none, @string, list, set, zset, hash, stream }
public enum RoleType { Master, Slave, Sentinel }
public clast KeyValue
{
public readonly string key;
public readonly T value;
public KeyValue(string key, T value) { this.key = key; this.value = value; }
}
public clast ScanResult
{
public readonly long cursor;
public readonly T[] items;
public readonly long length;
public ScanResult(long cursor, T[] items) { this.cursor = cursor; this.items = items; this.length = items.LongLength; }
}
}