Reputation: 30165
What I wouldn't give to have this work:
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)
Unfortunately, it calls the generic entry point rather than the "specialized" entry point. Well, thats interfaces for you.
I've also tried the same approach but replacing the int-specific signature with a generic signature that is Mojo
specific:
public void Handle<T>(T arg) where T : Mojo {}
I was hoping this would be sufficient to form a "special override" if the argument was of type Mojo
. But now the compiler complains that I have two methods with the same signature (one that is Mojo
specific, the other open-ended). Well, I was actually hoping it would think it was "the same signature" so that both would fulfill the interface and the "best" would be selected at run-time. Ah well.
In effect, I'm trying to achieve is vaguely similar to "Traits," which are the "else-if-then of C++". I guess it could also be considered a form of "interface signature contravariance."
I'd love to discover there is a special C# keyword that enables this capability, or that it is a featured addition to C# in .net 4.5.
Yes, no? Comments?
Upvotes: 3
Views: 540
Reputation: 109567
Try changing your Worker
class to this:
public class Worker
{
public void DoSomething(ICallBack cb)
{
((dynamic)cb).Handle(55);
}
}
[EDIT]
Just so you know, adding that innocuous-looking "dynamic" severely changes the output code. It effectively invokes the compiler at run-time to do the dynamic thing.
I also draw your attention to the comments and other answers here. I recommend you read them and understand why doing the above might not be such a great idea.
Further, as noted in the answer below, constraining the argument type to ICallBack
will still allow runtime errors if the Handle()
method is implemented explicitly.
Here's the IL for that simple-looking method:
.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
}
Upvotes: 3
Reputation: 391336
No this is not possible.
When the compiler compiles the type implementing the interface, it will create an interface map detailing which methods of the type that is linked to each method of the interface. This cannot be changed at runtime at will.
This means that whenever you call your Handle
method through that interface, it will always go to the same method on the underlying type, regardless of any other methods you feel should be more appropriate.
If you want the underlying type to call specific methods internally, depending on the specific type of the generic parameter, you will have to implement that yourself, either using dynamic dispatch, or by using if-statements or similar to detect which type of T you have and call the appropriate method.
The answer here that says you can cast the type you're calling the method on to dynamic
means you're using reflection to bypass the interface alltogether. The interface might as well not have any methods at all for this particular scenario, the cast to dynamic
will still "work".
I don't recommend this approach. You're effectively writing code that assumes it has carte blanche access to all methods of the underlying type, even though it specifically says "I only need this interface".
Additionally, if the only goal was to avoid runtime errors, consider what will happen if you implement the method explicitly in the class:
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);
}
}
This will crash at runtime with:
RuntimeBinderException:
'UserQuery.MessageHandler' does not contain a definition for 'Handle'
You can test the above code in LINQPad.
Upvotes: 5