Передача параметров С#, которые могут "соответствовать" интерфейсу, но на самом деле не реализуют его

Примечание. Я знаю, что на практике это ужасная идея; Мне просто интересно, что CLR позволяет вам делать, с целью создания своего рода "модифицировать класс после создания" препроцессора.

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

class Person {
    public string Greet() => "Hello!";
}

Теперь я определяю интерфейс и метод, например:

interface IGreetable {
    string Greet();
} 

// ...

void PrintGreeting(IGreetable g) => Console.WriteLine(g.Greet());

Класс Person не реализует реализацию IGreetable, но он может обойтись без каких-либо изменений в его методах.

Таким образом, есть ли какой-либо способ, используя Reflection, DLR или что-нибудь еще, в котором экземпляр Person может успешно передаваться в PrintGreeting без изменения какого-либо из вышеперечисленного кода?

Ответы

Ответ 1

Попробуйте использовать библиотеку Impromptu-Interface

[Интерфейс Impromptu-Interface], позволяющий вам обернуть любой объект (статический или динамический) со статическим интерфейсом, даже если он не наследует его. Он делает это путем испускания кэшированного динамического кода привязки внутри прокси.

Это позволяет сделать что-то вроде этого:

var person = new Person();
var greeter = person.ActLike<IGreetable>();

Ответ 2

Вы можете использовать объект dynamic оболочки, чтобы связать это самостоятельно, но вы потеряете безопасность типа внутри класса упаковки:

class GreetableWrapper : IGreetable
{
    private dynamic _wrapped;
    public GreetableWrapper(dynamic wrapped)
    {
        _wrapped = wrapped;
    }

    public string Greet()
    {
        return _wrapped.Greet();
    }
}

static void PrintGreeting(IGreetable g) => Console.WriteLine(g.Greet());
static void Main(string[] args)
{
    PrintGreeting(new GreetableWrapper(new Person()));
    Console.ReadLine();
}

Ответ 3

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

Самое близкое в С# прямо сейчас, возможно, так, как foreach работает с типом, у которого GetEnumerator() возвращает объект типа с MoveNext() и Current даже если они не реализуют IEnumerable и т.д., Только если это встроенная функция, в концепции, с которой работает компилятор, здесь вы можете определить их.

Интересно, что это также позволит вам определить статические члены.

Ответ 4

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

Если вы можете сделать это с помощью перенаправления, вы можете не реализовать что-то. И это противоречит методу безопасности, охватываемому.NET.

Ответ 5

Опция создает класс обертки над человеком и передает эту оболочку методу, оболочке нужно явно реализовать интерфейс.

Ответ 6

Если вы контролируете внешний код и готовы обертывать объект (и, похоже, все ответы здесь обертываются), динамическая привязка и библиотеки, такие как Impromptu-Interface, кажутся мне очень неприятными для чего-то, один лайнер.

class GreetablePerson : Person, IGreetable { }

И вы сделали.

Когда компилятор создает класс GreetablePerson, метод от Person заканчивает выполнение неявной реализации интерфейса, и все "просто работает". Единственное раздражение заключается в том, что код снаружи должен создавать экземпляры объектов GreetablePerson, но в стандартной объектно-ориентированной терминологии экземпляр GreetablePerson является экземпляром Person, поэтому это кажется мне правильным ответом на заданный вопрос.

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

Ответ 7

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

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