Дополнительные методы, перегружающиеся в C#, это работает?

Наличие класса, который имеет метод, как это:

class Window {
    public void Display(Button button) {
        // ...
    }
}

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

class WindowExtensions {
    public void Display(this Window window, object o) {
        Button button = BlahBlah(o);
        window.Display(button);
    }
}

То, что произошло, когда я попробовал, - то, что у меня есть бесконечная рекурсия. Существует ли способ заставить это работать? Я хочу, чтобы дополнительный метод был назван только, когда другой метод нельзя назвать.

6
задан 2 revs 1 October 2010 в 09:56
поделиться

5 ответов

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

Согласно этой статье :

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

Вы уверены, что вы явно проходите кнопку?

или дисплей пустоты (кнопка кнопки) рекурсивно вызывая себя?

1
ответ дан 9 December 2019 в 20:43
поделиться

Давайте перейдем к спецификации. Во-первых, мы должны понимать правила для вызовов метода. Примерно вы начинаете с типа, указанного экземпляром, вы пытаетесь вызвать метод. Вы поднимаетесь по цепочке наследования в поисках доступного метода. Затем вы делаете правила разрешения вашего типа и разрешение на перегрузку и вызовуте метод, если это удастся. Только если такой метод не найден, вы пытаетесь обработать метод в качестве метода расширения. Таким образом, из §7.5.5.2 (Усиливание метода расширения) см., В частности, смело смело:

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

Expr.identifier ()

EXPR . Идентификатор (args)

expr.identifier ()

expr.identifier (args)

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

Правила, помимо того, что становятся немного сложными, но для простого случая вы представляли нам, это довольно просто. Если применимый метод экземпляра, то метод расширения WindowSextsions.display (окно, объект) будет вызван. Метод экземпляра применим, если параметр Window.display - это кнопка или неявно , Chastable к кнопке. В противном случае будет вызван метод расширения (потому что все, что происходит из объекта , неявно отличается от объекта ).

Итак, если не будет важно, чтобы вы уходите, то, что вы пытаетесь сделать, будет работать.

Итак, рассмотрим следующий пример:

class Button { }
class Window {
    public void Display(Button button) {
        Console.WriteLine("Window.Button");
    }
}

class NotAButtonButCanBeCastedToAButton {
    public static implicit operator Button(
        NotAButtonButCanBeCastedToAButton nab
    ) {
        return new Button();
    }
}

class NotAButtonButMustBeCastedToAButton {
    public static explicit operator Button(
        NotAButtonButMustBeCastedToAButton nab
    ) {
        return new Button();
    }
}

static class WindowExtensions {
    public static void Display(this Window window, object o) {
        Console.WriteLine("WindowExtensions.Button: {0}", o.ToString());
        Button button = BlahBlah(o);
        window.Display(button);
    }
    public static Button BlahBlah(object o) {
        return new Button();
    }
}

class Program {
    static void Main(string[] args) {
        Window w = new Window();
        object o = new object();
        w.Display(o); // extension
        int i = 17;
        w.Display(i); // extension
        string s = "Hello, world!";
        w.Display(s); // extension
        Button b = new Button();
        w.Display(b); // instance
        var nab = new NotAButtonButCanBeCastedToAButton();
        w.Display(b); // implicit cast so instance
        var nabexplict = new NotAButtonButMustBeCastedToAButton();
        w.Display(nabexplict); // only explicit cast so extension
        w.Display((Button)nabexplict); // explictly casted so instance
    }
}

Это будет печать

WindowExtensions.Button: System.Object
Window.Button
WindowExtensions.Button: 17
Window.Button
WindowExtensions.Button: Hello, world!
Window.Button
Window.Button
Window.Button
WindowExtensions.Button: NotAButtonButMustBeCastedToAButton
Window.Button
Window.Button

на консоли.

9
ответ дан 9 December 2019 в 20:43
поделиться

Ну, я считаю, что это немного сложно. Если вы проходите кнопку в качестве параметра метода:

Button button = BlahBlah(o);
window.Display(button);

, то есть подходящий метод класса, который всегда имеет приоритет по методу расширения.

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

var o = new object();
window.Display(o);

Итак, из того, что я вижу, ваш пример должен работать правильно и метод расширения позвонит дисплею метода в окне . Бесконечная петля может быть вызвана каким-то другим кодом.

Есть ли какие-либо шанс, что окно , содержащее дисплей в вашем примере и в окне класс, который является параметром для метода расширения, на самом деле два разных класса ?

0
ответ дан 9 December 2019 в 20:43
поделиться

Это невозможно (также см. обезьяна для людей ) - может быть, с DLR и методом_missing .

0
ответ дан 9 December 2019 в 20:43
поделиться

Возможно, хотя надо быть осторожным с параметрами при перегрузках - обычно хорошо избегать типов объектов , так как это часто приводит к путанице в коде. Забавный способ, которым C# выбирает перегрузки, может привести вас в негодование. Он выберет 'более близкое' совпадение с типами, которые могут быть неявно приведены к 'дальнейшему', имеющему точное совпадение (см. этот вопрос).

Button myButton = // get button
Window currentWindow = // get window

// which method is called here?
currentWindow.Display( myButton );

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

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

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

Я бы изменил имя метода-расширения:

object somethingToMakeIntoAButton = // get object
Window currentWindow = // get window

// which method is called here?
currentWindow.DisplayButton( somethingToMakeIntoAButton );

Затем....

class WindowExtensions 
{
    public void DisplayButton(this Window window, object o) 
    {
        Button button = BlahBlah(o);

        // now it's clear to the C# compiler and human readers
        // that you want the instance method
        window.Display(button);
    }
}

Альтернативно, если бы второй параметр метода-расширения был типом, который не может быть неявно преобразован из Button (скажем, int или string), то такой путаницы тоже не случилось бы.

2
ответ дан 9 December 2019 в 20:43
поделиться