Метод расширения по сравнению с поведением метода родительского класса - PullRequest
2 голосов
/ 04 февраля 2010

Посмотрите на следующий код:

class A
{
    public string DoSomething(string str)
    {
        return "A.DoSomething: " + str;
    }
}

class B : A
{
}

static class BExtensions
{
    public static string DoSomething(this B b, string str)
    {
        return "BExtensions.DoSomething: " + str;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var a = new A();
        var b = new B();
        Console.WriteLine(a.DoSomething("test"));
        Console.WriteLine(b.DoSomething("test"));

        Console.ReadKey();
    }
}

Вывод кода:

A.DoSomething: test

A.DoSomething: test

Когда он компилируется, он не выдает предупреждений.

Мои вопросы: почему нет предупреждений, когда этот код компилируется, и что именно происходит, когда метод DoSomethingназывается?

Ответы [ 3 ]

6 голосов
/ 04 февраля 2010

Что происходит, когда метод вызывается, просто: просто вызов метода экземпляра. Поскольку C # имеет раннюю привязку, все методы разрешаются во время компиляции. Кроме того, методы экземпляра предпочтительнее методов расширения, поэтому ваш метод расширения никогда не вызывается.

См. это :

Вы можете использовать методы расширения для расширения класса или интерфейса, но не для их переопределения. Метод расширения с тем же именем и сигнатурой, что и у интерфейса или метода класса, никогда не будет вызываться. Во время компиляции методы расширения всегда имеют более низкий приоритет, чем методы экземпляра, определенные в самом типе.

Другими словами, если тип имеет метод с именем Process(int i), и у вас есть метод расширения с той же сигнатурой, компилятор всегда будет привязываться к методу экземпляра. Когда компилятор встречает вызов метода, он сначала ищет совпадение в методах экземпляра типа. Если совпадений не найдено, он будет искать любые методы расширения, определенные для этого типа, и связываться с первым найденным методом расширения.

4 голосов
/ 04 февраля 2010

Обычно компилятор всегда использует метод экземпляра, если он доступен, и прибегает к методам расширения только тогда, когда все остальное не удается.Из раздела 7.5.5.2 спецификации C # 3.0:

В вызове метода (§7.5.5.1) одной из форм

  • expr.идентификатор ()
  • expr.идентификатор (аргументы)
  • expr.идентификатор ()
  • expr.идентификатор (args)

, если обычная обработка вызова не находит применимых методов, делается попытка обработать конструкцию как вызов метода расширения.

Это одна из моих проблем с тем, как найти методы расширения ... метод расширения DoSomething не будет никогда вызываться как метод расширения (хотя его можно вызывать с обычным синтаксисом статического метода) ...и все же компилятор даже не выдает предупреждение: (

1 голос
/ 04 февраля 2010

Возможно, я не совсем прав, но компилятор делает что-то вроде этого: Когда дело доходит до

b.DoSomething("test")

он пытается найти метод с той же сигнатурой в этом или базовом классе, и когда он находит метод в базовом классе, сопоставляет вызов с ним, просто игнорируя метод расширения.

Но если вы, например, удалите базовый класс A из объявления B в той же строке, компилятор проверит, что в этом или базовом классе нет метода с такой сигнатурой, и заменит его вызовом статического метода BExtensions.DoSomething.

Вы можете проверить это с помощью .NET Reflector.

Когда B выводится из A:

.locals init (
    [0] class Test.A a,
    [1] class Test.B b)
...
ldloc.1 // loading local variable b
ldstr "test"
callvirt instance string Test.A::DoSomething(string)
call void [mscorlib]System.Console::WriteLine(string)

Когда B наследуется от System.Object:

.locals init (
    [0] class Test.A a,
    [1] class Test.B b)
...
ldloc.1 // loading local variable b
ldstr "test"
call string Test.BExtensions::DoSomething(class Test.B, string)
call void [mscorlib]System.Console::WriteLine(string)
...