Pasar parámetros de C# que pueden "encajar" en una interfaz, pero que en realidad no la implementan


Nota: Sé que esta es una idea horrible en la práctica; solo tengo curiosidad sobre lo que el CLR te permite hacer, con el objetivo de crear algún tipo de preprocesador 'modificar una clase después de crearla'.

Supongamos que tengo la siguiente clase, que se definió en otro ensamblado, por lo que no puedo cambiarla.

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

Ahora defino una interfaz, y un método, como el siguiente:

interface IGreetable {
    string Greet();
} 

// ...

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

La clase Person no implementa explícitamente IGreetable, pero podría prescindir de cualquier modificación de sus métodos.

Con eso, ¿hay alguna manera, usando Reflexión, el DLR o cualquier otra cosa, en la que una instancia de Person podría pasarse con éxito a PrintGreeting sin modificar ninguno de los códigos anteriores?

Author: Aaron Christiansen, 2018-01-29

7 answers

Intenta usar la biblioteca Impromptu-Interface

[The Impromptu-Interface] framework para permitirle envolver cualquier objeto (estático o dinámico) con una interfaz estática aunque no herede de ella. Lo hace emitiendo código de enlace dinámico en caché dentro de un proxy.

Esto te permite hacer algo como esto:

var person = new Person();
var greeter = person.ActLike<IGreetable>();
 29
Author: Roy Sanchez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-29 19:09:16

Puede usar un objeto dynamic wrapper para cablear esto usted mismo, pero pierde la seguridad de tipo dentro de la clase wrapping:

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();
}
 7
Author: John Koerner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-30 15:04:44

Esto puede ser bastante fácil pronto. Las clases de tipo se pueden introducir en C # como formas donde podrá definir características de una clase y código contra esa forma y luego hacer uso de su código con cualquier tipo que coincida sin que el autor de ese código tenga que declarar nada, más o menos como usted describe.

Lo más cercano en C# ahora es, quizás, cómo foreach funciona con un tipo que tiene un GetEnumerator() devuelve un objeto de un tipo con un MoveNext() y Current incluso ellos no implementan IEnumerable etc. solo mientras que ese es un concepto incorporado con el que el compilador se ocupa, aquí puede definirlos.

Curiosamente, también le permitirá definir miembros estáticos.

 6
Author: Jon Hanna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-30 04:16:01

No creo que esto sea posible. El compilador necesita ver algo que implemente explícitamente la interfaz o clase para que el compilador pueda confirmar que todo está implementado.

Si pudiera hacerlo usando redirección, podría fallar al implementar algo. Y eso va en contra del enfoque de seguridad adoptado por .NET.

 4
Author: Jonathan Wood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-29 18:48:17

Una opción es crear una clase wrapper sobre la persona y pasar este wrapper al método, el wrapper necesita implementar explícitamente la interfaz.

 4
Author: Mauri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-29 18:54:00

Si tienes el control del código externo, y estás dispuesto a envolver el objeto (y parece que todas las respuestas aquí envuelven), el enlace dinámico y las bibliotecas como Impromptu-Interface me parecen un montón de problemas para algo que es esencialmente un trazador de líneas.

class GreetablePerson : Person, IGreetable { }

Y ya está.

Cuando el compilador está construyendo la clase GreetablePerson, el método de Person termina haciendo una implementación implícita de la interfaz, y todo "simplemente funciona."El único irritación es que el código exterior tiene que instanciar GreetablePerson objetos, pero en la terminología orientada a objetos estándar, una instancia de GreetablePerson es una instancia de Person, por lo que esto me parece una respuesta válida a la pregunta como se hizo.

Si los requisitos se cambian de tal manera que también tenga instancias preexistentes de Person, entonces algo como Impromptu-Interface puede volverse más tentador, pero incluso entonces es posible que desee considerar dar GreetablePerson un constructor que copie de Person. Elegir el mejor camino a partir de ahí requiere obtener más detalles sobre los requisitos y los detalles de implementación reales de la clase Person en cuestión.

 4
Author: GrandOpener,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-30 07:40:05

En una especie de no relacionado, esto es algo que se hace comúnmente en otros idiomas, como Scala y Haskell.

Se conoce como usar lo que se llaman "clases de tipo". Las clases de tipo esencialmente le permiten definir el comportamiento de un tipo como si implementara explícitamente una interfaz, sin requerir que lo haga. Puedes leer más al respecto aquí.

 1
Author: wheeler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-30 02:34:10