几天写一个小程序的时候用到了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 }
这段代码功能本身没有什么问题,但是由于是用的反射来调用的接口函数,在大量调用的情况下可能有性能问题。(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: