2009-02-03 26 views
13

Me pregunto lo que se necesitaría para hacer algo como este trabajo:C# solicitud de función: implementar interfaces de tipos anónimos

using System; 

class Program 
{ 
    static void Main() 
    { 
     var f = new IFoo { 
        Foo = "foo", 
        Print =() => Console.WriteLine(Foo) 
      }; 
    } 
} 

interface IFoo 
{ 
    String Foo { get; set; } 
    void Print(); 
} 

El tipo anónimo creado sería algo como esto:

internal sealed class <>f__AnonymousType0<<Foo>j__TPar> : IFoo 
{ 
    readonly <Foo>j__TPar <Foo>i__Field; 

    public <>f__AnonymousType0(<Foo>j__TPar Foo) 
    { 
     this.<Foo>i__Field = Foo; 
    } 

    public <Foo>j__TPar Foo 
    { 
     get { return this.<Foo>i__Field; } 
    } 

    public void Print() 
    { 
     Console.WriteLine(this.Foo); 
    } 
} 

¿Hay alguna razón por la cual el compilador no podría hacer algo como esto? Incluso para métodos no válidos o métodos que toman parámetros, el compilador debería poder inferir los tipos de la declaración de interfaz.

Descargo de responsabilidad: Aunque me doy cuenta de que esto no es posible en la actualidad y tendría más sentido simplemente crear una clase concreta en este caso estoy más interesado en los aspectos teóricos de esto.

+0

Esta idea vino a mi mente ayer y trató de hacer una clase '' Anónimo y usar 'Reflection.Emit' para construir una clase tal aplicación de la interfaz de T. Pero es demasiaaaado caro! Luego probé suerte en SO y encontré esta pregunta, ¿tienes algún progreso por ahora? –

Respuesta

8

No habría algunos problemas con los miembros sobrecargados, los indexadores, e implementaciones de interfaz explícita.

Sin embargo, probablemente podría definir la sintaxis de una manera que le permita resolver esos problemas.

Curiosamente, puede obtener bastante cerca de lo que desea con C# 3.0 escribiendo una biblioteca. Básicamente, usted puede hacer esto:

Create<IFoo> 
(
    new 
    { 
     Foo = "foo", 
     Print = (Action)(() => Console.WriteLine(Foo)) 
    } 
); 

que es bastante cerca de lo que desea. Las principales diferencias son una llamada a "Crear" en lugar de la palabra clave "nueva" y el hecho de que necesita especificar un tipo de delegado.

La declaración de "Crear" se vería así:

T Create<T> (object o) 
{ 
//... 
} 

Sería entonces utilizar Reflection.Emit para generar una implementación de la interfaz dinámicamente en tiempo de ejecución.

Sintaxis, sin embargo, tiene problemas con las implementaciones de interfaz explícita y los miembros sobrecargados, que no podría resolver sin cambiar el compilador.

Una alternativa sería utilizar un inicializador de colección en lugar de un tipo anónimo. Que se vería así:

Create 
{ 
    new Members<IFoo> 
    { 
     {"Print", ((IFoo @this)=>Console.WriteLine(Foo))}, 
     {"Foo", "foo"} 
    } 
} 

que le permitiría:

  1. manija implementación de interfaz explícita especificando algo así como "IEnumerable.Current" para el parámetro de cadena.
  2. Defina miembros. Agregue para que no necesite especificar el tipo de delegado en el inicializador.

Usted tendría que hacer algunas cosas para poner en práctica esta:

  1. escritor un pequeño analizador de nombres de tipos de C#. Esto solo requiere ".", "[]", "<>", ID y los nombres de tipos primitivos, por lo que probablemente pueda hacerlo en unas pocas horas
  2. Implemente un caché para que solo genere una sola clase para cada interfaz única
  3. Implementa el código Reflection.Emit gen. Esto probablemente demore alrededor de 2 días como máximo.
+0

Esto estaba muy en el espíritu de la pregunta - ¡gracias! –

-1

Esto no sería posible actualmente.

¿Cuál sería la diferencia entre esto y simplemente hacer de IFoo una clase concreta en su lugar? Parece que esa podría ser la mejor opción.

¿Qué se necesitaría? Un nuevo compilador y toneladas de cheques para garantizar que no rompan las otras características. Personalmente, creo que sería más fácil exigir a los desarrolladores que simplemente creen una versión concreta de su clase.

2

Mientras elaboremos una lista de deseos de la interfaz, me gustaría poder decirle al compilador que una clase implementa una interfaz fuera de la definición de clase, incluso en un ensamblaje por separado.

Por ejemplo, supongamos que estoy trabajando en un programa para extraer archivos de diferentes formatos de archivo. Quiero poder implementar implementaciones existentes desde diferentes bibliotecas — decir, SharpZipLib y una implementación de PGP comercial — y consumir ambas bibliotecas usando el mismo código sin crear nuevas clases. Entonces podría usar tipos de cualquier fuente en restricciones genéricas, por ejemplo.

Otro uso estaría diciendo al compilador que implementa la interfaz System.Xml.Serialization.XmlSerializerSystem.Runtime.Serialization.IFormatter (ya lo hace, pero el compilador no lo sabe).

Esto podría utilizarse también para implementar su solicitud, simplemente no de forma automática. Todavía tendrías que decirle explícitamente al compilador al respecto. No estoy seguro de cómo se vería la sintaxis, porque aún tendría que mapear manualmente los métodos y las propiedades en algún lugar, lo que significa mucha verborrea. Tal vez algo similar a los métodos de extensión.

+0

¿eh? "Ya lo hace, pero el compilador no lo sabe" 1. No, no es así. 2. Implementa Serialize/Deserialize, pero no las tres propiedades Binder, Context, SurrogateSelector. –

+0

Eh: podrías calzarlo con bastante facilidad. Debería haberse creado para que pueda tener un método que acepte la instancia de IFormatter y pase BinaryFormatter, SoapFormatter, el XmlSerializer existente o su propia implementación de IFormatter sin quejarse. –

+0

¿Sabía que Oxygene [tiene esto] (http://prismwiki.codegear.com/en/Provide_Mixin-like_functionality)? También describí una característica que me gustaría ver [aquí] (http://codecrafter.blogspot.com/2010/10/roles-in-c.html). –

4

No se puede hacer que un tipo anónimo haga nada excepto tener propiedades de solo lectura.

Citando las C# Programming Guide (Anonymous Types):

"Los tipos anónimos son los tipos de clases que constan de uno o varios organismos públicos propiedades de sólo lectura No hay otros tipos de miembros de la clase, tales como métodos o eventos están permitidos.. Un tipo anónimo no se puede convertir a ninguna interfaz o tipo a excepción del objeto. "

+3

Sí, pero espero revisar esa definición :) –

0

Una idea interesante, me preocupa un poco que incluso si se puede hacer, podría ser confuso. P.ej.al definir una propiedad con setters y getters no triviales, o cómo desambiguar a Foo si el tipo declarante también contenía una propiedad llamada Foo.

Me pregunto si esto sería más fácil en un lenguaje más dinámico, o incluso con el tipo dinámico y DLR en C# 4.0?

Quizás hoy en C# parte de la intención se podría lograr con lambdas:

void Main() { 
    var foo = new Foo(); 
    foo.Bar = "bar"; 
    foo.Print =() => Console.WriteLine(foo.Bar); 
    foo.Print(); 
} 


class Foo : IFoo { 
    public String Bar { get; set; }  
    public Action Print {get;set;} 
} 
1

Usted podría tener algo como anonymous classes en Java:

using System; 

class Program { 
    static void Main() { 
    var f = new IFoo() { 
     public String Foo { get { return "foo"; } } 
     public void Print() { Console.WriteLine(Foo); } 
    }; 
    } 
} 

interface IFoo { 
    String Foo { get; set; } 
    void Print(); 
} 
-1

he utilizado en Java de la clase Amonimous a través de la "nueva IFoo() {...}" Sintaxis y es práctico y fácil cuando se tiene que poner en práctica rápida con una sencilla interfaz.

Como una muestra que sería bueno para implementar IDisposable esta manera en un objeto legado utilizado solo una vez en lugar de derivar una nueva clase ponerlo en práctica.

6

se requiere C# 4, pero el marco de código abierto impromptu interface puede falsificar esta fuera de la caja utilizando proxies DLR internamente. el rendimiento es bueno, aunque no tan buena como si el cambio se propuso que existían.

using ImpromptuInterface.Dynamic; 

...

var f = ImpromptuGet.Create<IFoo>(new{ 
       Foo = "foo", 
       Print = ReturnVoid.Arguments(() => Console.WriteLine(Foo)) 
      }); 
1

No sería esto fresco. Clase anónima en línea:

List<Student>.Distinct(new IEqualityComparer<Student>() 
{ 
    public override bool Equals(Student x, Student y) 
    { 
     return x.Id == y.Id; 
    } 

    public override int GetHashCode(Student obj) 
    { 
     return obj.Id.GetHashCode(); 
    } 
}) 
1

Voy a volcar esto aquí. Lo escribí hace un tiempo, pero IIRC funciona bien.

En primer lugar una función de ayuda para tomar una MethodInfo y devolver una Type de un juego o FuncAction. Necesitas una rama para cada número de parámetros, desafortunadamente, y aparentemente me detuve a las tres.

static Type GenerateFuncOrAction(MethodInfo method) 
{ 
    var typeParams = method.GetParameters().Select(p => p.ParameterType).ToArray(); 
    if (method.ReturnType == typeof(void)) 
    { 
     if (typeParams.Length == 0) 
     { 
      return typeof(Action); 
     } 
     else if (typeParams.Length == 1) 
     { 
      return typeof(Action<>).MakeGenericType(typeParams); 
     } 
     else if (typeParams.Length == 2) 
     { 
      return typeof(Action<,>).MakeGenericType(typeParams); 
     } 
     else if (typeParams.Length == 3) 
     { 
      return typeof(Action<,,>).MakeGenericType(typeParams); 
     } 
     throw new ArgumentException("Only written up to 3 type parameters"); 
    } 
    else 
    { 
     if (typeParams.Length == 0) 
     { 
      return typeof(Func<>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray()); 
     } 
     else if (typeParams.Length == 1) 
     { 
      return typeof(Func<,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray()); 
     } 
     else if (typeParams.Length == 2) 
     { 
      return typeof(Func<,,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray()); 
     } 
     else if (typeParams.Length == 3) 
     { 
      return typeof(Func<,,,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray()); 
     } 
     throw new ArgumentException("Only written up to 3 type parameters"); 
    } 
} 

Y ahora el método que toma una interfaz como un parámetro genérico y devuelve un Type que implementa la interfaz y tiene un constructor (necesita ser llamado a través de Activator.CreateInstance) teniendo un Func o Action para cada método/getter/setter. Sin embargo, necesitas saber el orden correcto para ponerlos en el constructor. Alternativamente (código comentado) puede generar una DLL que luego puede referenciar y usar el tipo directamente.

static Type GenerateInterfaceImplementation<TInterface>() 
{ 
    var interfaceType = typeof(TInterface); 
    var funcTypes = interfaceType.GetMethods().Select(GenerateFuncOrAction).ToArray(); 

    AssemblyName aName = 
     new AssemblyName("Dynamic" + interfaceType.Name + "WrapperAssembly"); 
    var assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
      aName, 
      AssemblyBuilderAccess.Run/*AndSave*/); // to get a DLL 

    var modBuilder = assBuilder.DefineDynamicModule(aName.Name/*, aName.Name + ".dll"*/); // to get a DLL 

    TypeBuilder typeBuilder = modBuilder.DefineType(
     "Dynamic" + interfaceType.Name + "Wrapper", 
      TypeAttributes.Public); 

    // Define a constructor taking the same parameters as this method. 
    var ctrBuilder = typeBuilder.DefineConstructor(
     MethodAttributes.Public | MethodAttributes.HideBySig | 
      MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, 
     CallingConventions.Standard, 
     funcTypes); 


    // Start building the constructor. 
    var ctrGenerator = ctrBuilder.GetILGenerator(); 
    ctrGenerator.Emit(OpCodes.Ldarg_0); 
    ctrGenerator.Emit(
     OpCodes.Call, 
     typeof(object).GetConstructor(Type.EmptyTypes)); 

    // For each interface method, we add a field to hold the supplied 
    // delegate, code to store it in the constructor, and an 
    // implementation that calls the delegate. 
    byte methodIndex = 0; 
    foreach (var interfaceMethod in interfaceType.GetMethods()) 
    { 
     ctrBuilder.DefineParameter(
      methodIndex + 1, 
      ParameterAttributes.None, 
      "del_" + interfaceMethod.Name); 

     var delegateField = typeBuilder.DefineField(
      "del_" + interfaceMethod.Name, 
      funcTypes[methodIndex], 
      FieldAttributes.Private); 

     ctrGenerator.Emit(OpCodes.Ldarg_0); 
     ctrGenerator.Emit(OpCodes.Ldarg_S, methodIndex + 1); 
     ctrGenerator.Emit(OpCodes.Stfld, delegateField); 

     var metBuilder = typeBuilder.DefineMethod(
      interfaceMethod.Name, 
      MethodAttributes.Public | MethodAttributes.Virtual | 
       MethodAttributes.Final | MethodAttributes.HideBySig | 
       MethodAttributes.NewSlot, 
      interfaceMethod.ReturnType, 
      interfaceMethod.GetParameters() 
       .Select(p => p.ParameterType).ToArray()); 

     var metGenerator = metBuilder.GetILGenerator(); 
     metGenerator.Emit(OpCodes.Ldarg_0); 
     metGenerator.Emit(OpCodes.Ldfld, delegateField); 

     // Generate code to load each parameter. 
     byte paramIndex = 1; 
     foreach (var param in interfaceMethod.GetParameters()) 
     { 
      metGenerator.Emit(OpCodes.Ldarg_S, paramIndex); 
      paramIndex++; 
     } 
     metGenerator.EmitCall(
      OpCodes.Callvirt, 
      funcTypes[methodIndex].GetMethod("Invoke"), 
      null); 

     metGenerator.Emit(OpCodes.Ret); 
     methodIndex++; 
    } 

    ctrGenerator.Emit(OpCodes.Ret); 

    // Add interface implementation and finish creating. 
    typeBuilder.AddInterfaceImplementation(interfaceType); 
    var wrapperType = typeBuilder.CreateType(); 
    //assBuilder.Save(aName.Name + ".dll"); // to get a DLL 

    return wrapperType; 
} 

Puede utilizar esto como p. Ej.

public interface ITest 
{ 
    void M1(); 
    string M2(int m2, string n2); 
    string prop { get; set; } 

    event test BoopBooped; 
} 

Type it = GenerateInterfaceImplementation<ITest>(); 
ITest instance = (ITest)Activator.CreateInstance(it, 
    new Action(() => {Console.WriteLine("M1 called"); return;}), 
    new Func<int, string, string>((i, s) => "M2 gives " + s + i.ToString()), 
    new Func<String>(() => "prop value"), 
    new Action<string>(s => {Console.WriteLine("prop set to " + s);}), 
    new Action<test>(eh => {Console.WriteLine(eh("handler added"));}), 
    new Action<test>(eh => {Console.WriteLine(eh("handler removed"));})); 

// or with the generated DLL 
ITest instance = new DynamicITestWrapper(
    // parameters as before but you can see the signature 
    ); 
Cuestiones relacionadas