阅读本文的预备知识:
1、一般情况下.net中delegate的具体类型会包含一个构造函数,一个Invoke方法,一个BeginInvoke方法和一个EndInvoke方法。
2、delegate可以被当成方法的载体,传入另一个方法,或者作为某个方法的返回值(参考函数式编程中的高阶函数的思想)。
3、delegate可以用于需要延迟求解的目的。
4、类型的显式/隐式转换
5、文中使用Lambda表达式来简化匿名方法,所以需要能看懂Lambda表达式。
下面开始正文:
既然,本文的标题说,要来一个delegate的另类玩法,当然就不能玩得太普通,不过,为了方便大家理解,先从一个常规的例子出发,把一个延迟的x和一个延迟的y的字符串值拼接起来:
public static string Concat(Func<string> x, Func<string> y)
{
return x() + y();
}
然后,开始调用这个Concat方法,直接从控制台读取2个字符串,然后拼接,最后再写出来:
Console.WriteLine(Concat(Console.ReadLine, Console.ReadLine));
但是,如果我某一个调用的一个参数是一个常量,或者已经被求解出来的值,为了调用这个方法,不得不这样写:
Console.WriteLine(Concat(() => "Hello ", Console.ReadLine));
虽然运用了Lambda表达式,但是代码还是显得有点啰嗦,期望的写法应该是:
Console.WriteLine(Concat("Hello ", Console.ReadLine));
这样,读者可以清晰的知道这里是把"Hello"拼接上一个委托的延迟求解的值,再输出到Console。
平时遇到这种情况时,通常可以借助类型的隐式转换,例如Linq to Xml中的XName就可以从一个string直接转换过来,很大程度上简化了编码的工作量。
但是,c#要求类型的隐式转换必须写在转换的原始类型方或转换的目标类型方,看一下这里的场景,原始类型是string,不能写,目标类型是委托(不必拘泥于Func<T>),也不能写,似乎隐式转换是用不上了,如果要用也必须借助第3个类型做中转,即,一个Foo<T>类,既可以从Func<T>隐式转换过来,也可以从T隐式转换过来,但是这样一个Foo<T>在遇到Lambda表达式的时候,就有点苍白了,因为编译器无法推断出这里是要把一个Lambda表达式推成Func<T>再隐式转换成Foo<T>,必须手动指出Lambda表达式的类型,显然,这个比之前的方式(把非延迟的值以Lambda表达式传入)更无法接受。
要是c#支持partial delegate就好了,可以很轻松的写成:
public partial delegate T Lazy<T>();
partial class Lazy<T>
{
private sealed class ValueHolder
{
private T value;
public ValueHolder(T value)
{
this.value = value;
}
public T GetValue()
{
return value;
}
}
public static implicit operator Lazy<T>(T value)
{
return new Lazy<T>((new ValueHolder(value)).GetValue);
}
}
遗憾的是在c#立即跳出错误,提示:“partial”修饰符只能出现在紧靠“class”、“struct”、“interface”或“void”前面的位置。这里c#编译器牢牢地限制住了具体delegate类型的行为,使具体的delegate必须继承自MulticastDelegate,并且只有1个构造函数和3个Invoke方法。
思考一下,c#不支持的就是.net不支持的吗?
答案当然是:不
.net是c#的一个超集,因此c#写不出来的类,也可以被其他语言写出来,当然一个和.net平台最一致的语言就是IL,因此,完全可以用IL写一个满足之前需求的delegate。不过,本人的习惯是用Emit,而不是写IL,其实两者相差也不是很多。废话就不说了,来Emit这样一个delegate(篇幅较长,80多行):
Code
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("SuperDelegates"), AssemblyBuilderAccess.Save);
6 var module = assembly.DefineDynamicModule("SuperDelegates", "SuperDelegates.netmodule");
7 CreateLazy(module);
8 assembly.Save("SuperDelegates.dll");
9 }
10
11 private static void CreateLazy(ModuleBuilder module)
12 {
13 var lazy = module.DefineType("SuperDelegates.Lazy`1",
14 TypeAttributes.AutoClass | TypeAttributes.Sealed | TypeAttributes.Public | TypeAttributes.Serializable,
15 typeof(MulticastDelegate));
16 var lazyT = lazy.DefineGenericParameters("T")[0];
17 //生成delegate的标准构造函数
18 var lazyCtor = DefineConstructor(lazy);
19 //生成delegate的3个标准方法
20 var lazyInvoke = DefineMethod(lazy, "Invoke", lazyT, Type.EmptyTypes);
21 var lazyBeginInvoke = DefineMethod(lazy, "BeginInvoke", typeof(IAsyncResult), new Type[] { typeof(AsyncCallback), typeof(object) });
22 var lazyEndInvoke = DefineMethod(lazy, "EndInvoke", lazyT, new Type[] { typeof(IAsyncResult) });
23 //生成ValueHolder类
24 var valueHolder = lazy.DefineNestedType("ValueHolder`1",
25 TypeAttributes.AutoClass | TypeAttributes.Sealed | TypeAttributes.NestedPrivate | TypeAttributes.Serializable,
26 typeof(object));
27 var valueHolderT = valueHolder.DefineGenericParameters("T")[0];
28 var valueHolderField = valueHolder.DefineField("value", valueHolderT, FieldAttributes.Private);
29 var valueHolderCtor = valueHolder.DefineConstructor(
30 MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
31 CallingConventions.Standard, new Type[] { valueHolderT });
32 {
33 var il = valueHolderCtor.GetILGenerator();
34 il.Emit(OpCodes.Ldarg_0);
35 il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
36 il.Emit(OpCodes.Ldarg_0);
37 il.Emit(OpCodes.Ldarg_1);
38 il.Emit(OpCodes.Stfld, valueHolderField);
39 il.Emit(OpCodes.Ret);
40 }
41 var valueHolderGetValue = valueHolder.DefineMethod("GetValue",
42 MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.SpecialName, valueHolderT, Type.EmptyTypes);
43 {
44 var il = valueHolderGetValue.GetILGenerator();
45 il.Emit(OpCodes.Ldarg_0);
46 il.Emit(OpCodes.Ldfld, valueHolderField);
47 il.Emit(OpCodes.Ret);
48 }
49 valueHolder.CreateType();
50 //生成隐式转换
51 MethodBuilder op_Implicit = lazy.DefineMethod("op_Implicit",
52 MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, lazy, new Type[] { lazyT });
53 {
54 var il = op_Implicit.GetILGenerator();
55 il.Emit(OpCodes.Ldarg_0);
56 il.Emit(OpCodes.Newobj, valueHolderCtor);
57 il.Emit(OpCodes.Dup);
58 il.Emit(OpCodes.Ldvirtftn, valueHolderGetValue);
59 il.Emit(OpCodes.Newobj, lazyCtor);
60 il.Emit(OpCodes.Ret);
61 }
62 lazy.CreateType();
63 }
64
65 private static ConstructorBuilder DefineConstructor(TypeBuilder builder)
66 {
67 ConstructorBuilder cb = builder.DefineConstructor(
68 MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
69 CallingConventions.Standard, new Type[] { typeof(object), typeof(IntPtr) });
70 cb.SetImplementationFlags(MethodImplAttributes.Managed | MethodImplAttributes.Runtime);
71 return cb;
72 }
73
74 private static MethodBuilder DefineMethod(TypeBuilder builder, string name, Type returnType, Type[] parameters)
75 {
76 MethodBuilder mb = builder.DefineMethod(name,
77 MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, returnType, parameters);
78 mb.SetImplementationFlags(MethodImplAttributes.Managed | MethodImplAttributes.Runtime);
79 return mb;
80 }
81
82
83 } 这段代码的运行结果就是创建了一个叫SuperDelegates的assembly(注意SuperDelegates.netmodule也是这个assembly的一部分,别漏掉了),然后,添加引用,重写之前的方法(其实就是更换委托类型而已):
public static string Concat(Lazy<string> x, Lazy<string> y)
{
return (string)x + (string)y;
}
注意,这里的Lazy<T>是可以从T类型直接隐式转换过来的,因此,可以这样调用:
Console.WriteLine(Concat("Hello ", Console.ReadLine));
这里的"Hello "被隐式的转换成一个类似() => "Hello "。
再来个例子:
Code
static void Main(string[] args)
{
Console.WriteLine(IIF(ReadInt() > 0, ReadInt, 0));
Console.Write("Press <Enter> to exit.");
Console.ReadLine();
}
public static int ReadInt()
{
Console.Write("Input a number:");
return int.Parse(Console.ReadLine());
}
public static int IIF(bool condition, Lazy<int> trueValue, Lazy<int> falueValue)
{
if (condition)
return trueValue();
else
return falueValue();
} 当然,这个Lazy<T>可以被玩得更复杂,这里仅仅是抛砖引玉,另外,本文的重点也不是如何玩delegate,而是要破除大家心里的具体delegate就只有这些方法的思想禁锢,希望大家能从本文中获得某些启发。