in the following code I define an interface, an abstract base class with a me开发者_JAVA技巧thod that prints "foo", a class that implements both, and an extension method on the interface with a signature equal to a method in the abstract base class that prints "bar". when I run this sample, why is "bar" printed instead of "foo"? and if applicable, what's the morale behind this language design choice?
public interface ISomething
{}
public abstract class SomethingElse
{
public void foo()
{
Console.WriteLine("foo");
}
}
public class DefinitelySomething : SomethingElse, ISomething
{}
public static class ISomethingExtensions
{
public static void foo(this ISomething graphic)
{
Console.WriteLine("bar");
}
}
class Program
{
static void Main(string[] args)
{
ISomething g = new DefinitelySomething();
g.foo();
}
}
ISomething
does not have a member called foo
, so the extension method will be called.
Because the variable is declared as ISomething
.
The instance method is not known until run-time however the method overload resolution is at compile time. There's no guarantee that the instance actually has a suitable method. In you particular example it has but that's from a type safety perspective an irrelevant coincidence. The important type at the line g.foo()
is the type that g is declared as not the run time type
The foo
method on SomethingElse
is not considered for overload resolution as you are acting on an instance of ISomething
.
Or to think about it another way, consider what would happen if you didn't have any extension method? In this case you would get a compile error as no other suitable method could be found.
Try changing your code to the following:
DefinitelySomething g = new DefinitelySomething();
g.foo();
And it should behave how you are expecting.
In this case you call the foo
method of the ISomething
, and it is resolved into extension method.
If you use this code:
ISomething g = new DefinitelySomething();
(g as SomethingElse).foo();
you'll get the right output.
You could always check inside the extension method to see if the object you are currently using has an existing method with the exact same signature
public static System.Reflection.MethodInfo ExtensionOverrider(this Object obj, System.Reflection.MethodInfo method )
{
return obj.GetType().GetMethods().Where(
x => x.Name == method.Name &&
x.GetParameters().Select(z => z.ParameterType).SequenceEqual(method.GetParameters().Skip(1).Select(w => w.ParameterType))).FirstOrDefault();
}
public static void foo(this ISomething graphic)
{
var Method = graphic.ExtensionOverrider(System.Reflection.MethodBase.GetCurrentMethod());
if( Method != null)
Method.Invoke(graphic, new Object[0]{});
else
Console.WriteLine("bar");
}
Just some more examples, tested in LinqPad.
Note that the items declared as ISomething
use the extension method and return "bar", which as @leppie pointed out is because ISomething
does not have a method foo
, but calling foo
on the new instance directly gives the correct output "foo".
void Main(string[] args) {
// baseline
var g0 = new DefinitelyNothing(); g0.foo(); // "foo"
ISomething g;
// won't work, not ISomething
// g = new DefinitelyNothing(); g.foo();
g = new DefinitelyNothingThatThinksItsSomething(); g.foo(); // "bar"
new DefinitelyNothingThatThinksItsSomething().foo(); // "foo"
g = new DefinitelySomething(); g.foo(); // "bar"
new DefinitelySomething().foo(); // "foo"
g = new DefinitelySomethingWithFoo(); g.foo(); // "bar"
new DefinitelySomethingWithFoo().foo(); // "foo"
(g as IWithFoo).foo(); // "foo", not "qux"
g = new DefinitelySomethingFromNothingWithFoo(); g.foo(); // "bar"
new DefinitelySomethingFromNothingWithFoo().foo(); // "foo"
(g as ISomethingWithFoo).foo(); // "foo", not "baz"
IWithFoo g1;
g1 = new DefinitelyNothingWithFoo(); g1.foo(); // "foo"
new DefinitelyNothingWithFoo().foo(); // "foo"
}
public interface ISomething {}
public interface IWithFoo {
void foo();
}
public interface ISomethingWithFoo : ISomething, IWithFoo {}
public abstract class Nothing {
public void foo() { Console.WriteLine("foo"); }
}
public abstract class NothingWithFoo : IWithFoo {
public void foo() { Console.WriteLine("foo"); }
}
public abstract class Something : ISomething {
public void foo() { Console.WriteLine("foo"); }
}
public abstract class SomethingWithFoo : ISomethingWithFoo {
public void foo() { Console.WriteLine("foo"); }
}
public abstract class SomethingFromNothingWithFoo : Nothing, ISomethingWithFoo {}
public class DefinitelyNothing: Nothing {}
public class DefinitelyNothingThatThinksItsSomething: Nothing, ISomething {}
public class DefinitelyNothingWithFoo : NothingWithFoo {}
public class DefinitelySomething : Something {}
public class DefinitelySomethingWithFoo : SomethingWithFoo {}
public class DefinitelySomethingFromNothingWithFoo : SomethingFromNothingWithFoo {}
public static class ISomethingExtensions {
// http://en.wikipedia.org/wiki/Metasyntactic_variable
public static void foo(this ISomething whatever) { Console.WriteLine("bar"); }
public static void foo(this ISomethingWithFoo whatever) { Console.WriteLine("baz"); }
public static void foo(this IWithFoo whatever) { Console.WriteLine("qux"); }
}
精彩评论