通用直通接口.这可能吗?
本文关键字:直通 接口 | 更新日期: 2023-09-27 18:13:04
我愿意付出一切来换取这份工作:
public interface ICallBack
{
void Handle<T>(T arg);
}
public class MessageHandler : ICallBack
{
public void Handle<T>(T arg)
{
string name = typeof(T).Name;
Console.WriteLine(name);
}
public void Handle(int arg)
{
string name = "wow, an int";
Console.WriteLine(name);
}
}
public class Worker
{
public void DoSomething(ICallBack cb)
{
cb.Handle(55);
}
}
//...
Worker foo = new Worker();
ICallBack boo = new MessageHandler();
//I want this to print "Wow, an int"
foo.DoSomething(boo)
不幸的是,它调用通用入口点而不是"专门化"入口点。这就是你的接口。
我也尝试过同样的方法,但用Mojo
特定的通用签名替换int特定的签名:
public void Handle<T>(T arg) where T : Mojo {}
我希望这将足以形成一个"特殊覆盖",如果参数是类型Mojo
。但是现在编译器抱怨我有两个方法具有相同的签名(一个是Mojo
特定的,另一个是开放式的)。嗯,我实际上希望它会认为这是"相同的签名",这样两者都能满足接口,并且在运行时选择"最佳"。啊好。
实际上,我试图实现的是模糊类似于"特征",这是c++的"else-if-then"。我想它也可以被认为是"接口签名逆变"的一种形式。
我很想发现有一个特殊的c#关键字可以实现这个功能,或者它是c#在。net 4.5中的一个特色添加。
是的,不是吗?评论?
不,这是不可能的
当编译器编译实现该接口的类型时,它将创建一个接口映射,详细说明该类型的哪些方法链接到该接口的每个方法。这不能在运行时随意更改。
这意味着无论何时您通过该接口调用Handle
方法,它都将始终转到底层类型上的相同方法,而不管您认为其他任何方法应该更合适。
如果你想让底层类型在内部调用特定的方法,根据泛型参数的特定类型,你必须自己实现,要么使用动态调度,要么使用If语句或类似的方法来检测T的类型并调用适当的方法。
这里的答案说你可以将你调用方法的类型强制转换为dynamic
,这意味着你正在使用反射来绕过接口。对于这种特殊的场景,接口可能根本没有任何方法,转换到dynamic
仍然会"工作"。
我不推荐这种方法。你实际上是在编写代码,假设它可以全权访问底层类型的所有方法,即使它明确地说"我只需要这个接口"。
此外,如果唯一的目标是避免运行时错误,请考虑如果在类中显式实现该方法将会发生什么:
void Main()
{
Worker foo = new Worker();
ICallBack boo = new MessageHandler();
foo.DoSomething(boo);
}
public interface ICallBack
{
void Handle<T>(T arg);
}
public class MessageHandler : ICallBack
{
void ICallBack.Handle<T>(T arg)
{
string name = typeof(T).Name;
Console.WriteLine(name);
}
}
public class Worker
{
public void DoSomething(ICallBack cb)
{
((dynamic)cb).Handle(55);
}
}
这将在运行时崩溃:
RuntimeBinderException :
"UserQuery。MessageHandler'不包含'Handle'的定义
您可以在LINQPad中测试上述代码。
试着改变你的Worker
类:
public class Worker
{
public void DoSomething(ICallBack cb)
{
((dynamic)cb).Handle(55);
}
}
[编辑]正如您所知,添加看起来无害的"动态"会严重改变输出代码。它有效地在运行时调用编译器来做动态的事情。
我也提请你注意这里的评论和其他答案。我建议你读一读,并理解为什么这样做可能不是一个好主意。
此外,如下面的答案所述,如果显式实现Handle()
方法,将参数类型限制为ICallBack
仍然会导致运行时错误。
下面是这个看起来很简单的方法的IL:
.method public hidebysig instance void DoSomething(class ConsoleApplication1.ICallBack cb) cil managed
{
.maxstack 9
.locals init (
[0] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000)
L_0000: nop
L_0001: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>> ConsoleApplication1.Worker/<DoSomething>o__SiteContainer0::<>p__Site1
L_0006: brtrue L_0058
L_000b: ldc.i4 256
L_0010: ldstr "Handle"
L_0015: ldnull
L_0016: ldtoken ConsoleApplication1.Worker
L_001b: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
L_0020: ldc.i4.2
L_0021: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
L_0026: stloc.0
L_0027: ldloc.0
L_0028: ldc.i4.0
L_0029: ldc.i4.0
L_002a: ldnull
L_002b: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
L_0030: stelem.any [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
L_0035: ldloc.0
L_0036: ldc.i4.1
L_0037: ldc.i4.3
L_0038: ldnull
L_0039: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
L_003e: stelem.any [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
L_0043: ldloc.0
L_0044: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>, class [mscorlib]System.Type, class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
L_0049: call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
L_004e: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>> ConsoleApplication1.Worker/<DoSomething>o__SiteContainer0::<>p__Site1
L_0053: br L_0058
L_0058: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>> ConsoleApplication1.Worker/<DoSomething>o__SiteContainer0::<>p__Site1
L_005d: ldfld !0 [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>>::Target
L_0062: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>> ConsoleApplication1.Worker/<DoSomething>o__SiteContainer0::<>p__Site1
L_0067: ldarg.1
L_0068: ldc.i4.s 12
L_006a: callvirt instance void [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>::Invoke(!0, !1, !2)
L_006f: nop
L_0070: ret
}