• 让SignalR客户端回调支持强类型


    几天写一个小程序的时候用到了SignalR,发现现在SingalR Server 支持强类型了,也就是说,我们可以定义一个客户端的通知契约:

        public interface IClient
        {
            void SayHello(string message);
        }

    然后Hub就可以这么写了:

        public class MessageHub : Hub<IClient>
        {
            public void Hello(string message)
            {
                Clients.All.SayHello(message);        //Clients.All
    现在不是
    dynamic的了
            }
        }

    主动通知也是强类型的了。

        public static void notify(string message)
        {
            var context = GlobalHost.ConnectionManager.GetHubContext<MessageHub, IClient>();
            context.Clients.All.SayHello(message);
        }

    有强类型检查后感觉方便多了。但是SignalR Client却没有这个待遇,依然是这种手动关联的形式:

        var proxy = connection.CreateHubProxy("MessageHub");
        proxy.On<string>("SayHello", i => Console.WriteLine(i));

    这种方式不够友好,因此我写了一个扩展函数,使得在客户端也可以使用强类型。使用方法如下:

        var proxy = connection.CreateHubProxy("MessageHub");
        proxy.Subcribe<IClient>(new ClientNotify());

        public interface Iclient
        {
            void SayHello(string message);
        }

        public class ClientNotify : Iclient
        {
            public void SayHello(string message)
            {
                Console.WriteLine(message);
            }
        }

    代码如下(随手写的,质量较低,有需要的朋友自行重构下): 

     1     static class StrongTypeProxyExtension
     2     {
     3         public static IDisposable Subcribe<T>(this IHubProxy proxy, T obj)
     4         {
     5             var disposer = new Disposer();
     6 
     7             foreach (var method in typeof(T).GetMethods())
     8             {
     9                 Subcribe(proxy, obj, method, disposer);
    10             }
    11 
    12             return disposer;
    13         }
    14 
    15 
    16         static void Subcribe<T>(IHubProxy proxy, T obj, MethodInfo method,  Disposer disposer)
    17         {
    18             var subscription = proxy.Subscribe(method.Name);
    19             var methodParas = method.GetParameters().Select(i => i.ParameterType).ToArray();
    20 
    21             var invokeHandler = new Action<object[]>(para => method.Invoke(obj, para));
    22 
    23             Action<IList<JToken>> handler = args =>
    24             {
    25                 onData(methodParas, args, proxy.JsonSerializer, invokeHandler);
    26             };
    27 
    28             subscription.Received += handler;
    29             disposer.Add(() => subscription.Received -= handler);
    30         }
    31 
    32         static void onData(Type[] paraTypes, IList<JToken> data, JsonSerializer serializer, Action<object[]> invokeHandler)
    33         {
    34             if (paraTypes.Length != data.Count)
    35                 throw new InvalidOperationException();
    36 
    37             var para = data.Zip(paraTypes, (i1, i2) => i1.ToObject(i2)).ToArray();
    38             invokeHandler(para);
    39         }
    40 
    41         class Disposer : List<Action>, IDisposable
    42         {
    43             public void Dispose()
    44             {
    45                 foreach (var disposeHandler in this)
    46                 {
    47                     disposeHandler();
    48                 }
    49             }
    50         }
    51     }
    View Code

    这段代码功能本身没有什么问题,但是由于是用的反射来调用的接口函数,在大量调用的情况下可能有性能问题。(Subcribe函数中)

        var invokeHandler = new Action<object[]>(para => method.Invoke(obj, para));

    对于有性能要求的朋友,可以使用FastInvokeHandler来优化这一性能,它是使用的Emit实现的,试了一下,基本上和原生调用在一个数量级。由于CodeProject可能会由于方校长抖威风而不定时迁移到火星。这里我把相关代码摘录了下来(稍微改动了点): 

      1     using InvokeHandler = Func<object, object[], object>;
      2 
      3     class FastInvokeHandler
      4     {
      5         public static InvokeHandler Create(MethodInfo methodInfo)
      6         {
      7             DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) }, methodInfo.DeclaringType.Module);
      8             ILGenerator il = dynamicMethod.GetILGenerator();
      9             ParameterInfo[] ps = methodInfo.GetParameters();
     10             Type[] paramTypes = new Type[ps.Length];
     11             for (int i = 0; i < paramTypes.Length; i++)
     12             {
     13                 if (ps[i].ParameterType.IsByRef)
     14                     paramTypes[i] = ps[i].ParameterType.GetElementType();
     15                 else
     16                     paramTypes[i] = ps[i].ParameterType;
     17             }
     18             LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];
     19 
     20             for (int i = 0; i < paramTypes.Length; i++)
     21             {
     22                 locals[i] = il.DeclareLocal(paramTypes[i], true);
     23             }
     24             for (int i = 0; i < paramTypes.Length; i++)
     25             {
     26                 il.Emit(OpCodes.Ldarg_1);
     27                 EmitFastInt(il, i);
     28                 il.Emit(OpCodes.Ldelem_Ref);
     29                 EmitCastToReference(il, paramTypes[i]);
     30                 il.Emit(OpCodes.Stloc, locals[i]);
     31             }
     32             if (!methodInfo.IsStatic)
     33             {
     34                 il.Emit(OpCodes.Ldarg_0);
     35             }
     36             for (int i = 0; i < paramTypes.Length; i++)
     37             {
     38                 if (ps[i].ParameterType.IsByRef)
     39                     il.Emit(OpCodes.Ldloca_S, locals[i]);
     40                 else
     41                     il.Emit(OpCodes.Ldloc, locals[i]);
     42             }
     43             if (methodInfo.IsStatic)
     44                 il.EmitCall(OpCodes.Call, methodInfo, null);
     45             else
     46                 il.EmitCall(OpCodes.Callvirt, methodInfo, null);
     47             if (methodInfo.ReturnType == typeof(void))
     48                 il.Emit(OpCodes.Ldnull);
     49             else
     50                 EmitBoxIfNeeded(il, methodInfo.ReturnType);
     51 
     52             for (int i = 0; i < paramTypes.Length; i++)
     53             {
     54                 if (ps[i].ParameterType.IsByRef)
     55                 {
     56                     il.Emit(OpCodes.Ldarg_1);
     57                     EmitFastInt(il, i);
     58                     il.Emit(OpCodes.Ldloc, locals[i]);
     59                     if (locals[i].LocalType.IsValueType)
     60                         il.Emit(OpCodes.Box, locals[i].LocalType);
     61                     il.Emit(OpCodes.Stelem_Ref);
     62                 }
     63             }
     64 
     65             il.Emit(OpCodes.Ret);
     66             InvokeHandler invoder = (InvokeHandler)dynamicMethod.CreateDelegate(typeof(InvokeHandler));
     67             return invoder;
     68         }
     69 
     70         private static void EmitCastToReference(ILGenerator il, System.Type type)
     71         {
     72             if (type.IsValueType)
     73             {
     74                 il.Emit(OpCodes.Unbox_Any, type);
     75             }
     76             else
     77             {
     78                 il.Emit(OpCodes.Castclass, type);
     79             }
     80         }
     81 
     82         private static void EmitBoxIfNeeded(ILGenerator il, System.Type type)
     83         {
     84             if (type.IsValueType)
     85             {
     86                 il.Emit(OpCodes.Box, type);
     87             }
     88         }
     89 
     90         private static void EmitFastInt(ILGenerator il, int value)
     91         {
     92             switch (value)
     93             {
     94                 case -1:
     95                     il.Emit(OpCodes.Ldc_I4_M1);
     96                     return;
     97                 case 0:
     98                     il.Emit(OpCodes.Ldc_I4_0);
     99                     return;
    100                 case 1:
    101                     il.Emit(OpCodes.Ldc_I4_1);
    102                     return;
    103                 case 2:
    104                     il.Emit(OpCodes.Ldc_I4_2);
    105                     return;
    106                 case 3:
    107                     il.Emit(OpCodes.Ldc_I4_3);
    108                     return;
    109                 case 4:
    110                     il.Emit(OpCodes.Ldc_I4_4);
    111                     return;
    112                 case 5:
    113                     il.Emit(OpCodes.Ldc_I4_5);
    114                     return;
    115                 case 6:
    116                     il.Emit(OpCodes.Ldc_I4_6);
    117                     return;
    118                 case 7:
    119                     il.Emit(OpCodes.Ldc_I4_7);
    120                     return;
    121                 case 8:
    122                     il.Emit(OpCodes.Ldc_I4_8);
    123                     return;
    124             }
    125 
    126             if (value > -129 && value < 128)
    127             {
    128                 il.Emit(OpCodes.Ldc_I4_S, (SByte)value);
    129             }
    130             else
    131             {
    132                 il.Emit(OpCodes.Ldc_I4, value);
    133             }
    134         }
    135     }
    View Code

    有了这段代码后,然后把前面的Subcribe函数反射调用改写如下形式即可

        var fastMehod = FastInvokeHandler.Create(method);
        var invokeHandler = new Action<object[]>(para => fastMehod(obj, para));

    另外,github上也有人写了一个客户端强类型的扩展,功能要完善一点(支持客户端调用服务器端方法,我一般都是用的通知,就懒得弄了),不过我觉得它的使用方式还是有点麻烦,感兴趣的朋友可以看下,地址是https://github.com/i-e-b/SignalR-TypeSafeClient 。

  • 相关阅读:
    echars 折线图使用
    echars 饼图使用
    React native中DrawerNavigator,StackNavigator,TabNavigator导航栏使用
    React native 中 SectionList用法
    React native 中使用Fetch请求数据
    React native中使用XMLHttpRequest请求数据
    实现在WebView中返回上一级
    DatePickerAndroid用法
    Lambda
    .Net常见线程安全问题整理
  • 原文地址:https://www.cnblogs.com/TianFang/p/3997176.html
Copyright © 2020-2023  润新知