C#功能请求:在匿名类型上实现接口

时间:2009-02-03 20:51:06

标签: c# compiler-construction anonymous-types

我想知道如何做这样的工作:

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();
}

创建的匿名类型看起来像这样:

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);
    }
}

有没有理由让编译器无法做到这样的事情?即使对于采用参数的非void方法或方法,编译器也应该能够从接口声明中推断出类型。

免责声明:虽然我确实认识到目前这不可行,但在这种情况下简单地创建一个具体类会更有意义,我对此理论方面更感兴趣。

10 个答案:

答案 0 :(得分:9)

重载成员,索引器和显式接口实现会出现一些问题。

但是,您可以以允许您解决这些问题的方式定义语法。

有趣的是,通过编写库,您可以非常接近C#3.0所需的内容。基本上,你可以这样做:

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

这与你想要的非常接近。主要区别是调用“创建”而不是“新”关键字,以及需要指定委托类型的事实。

“创建”的声明如下:

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

然后,它将使用Reflection.Emit在运行时动态生成接口实现。

但是,这种语法在显式接口实现和重载成员方面确实存在问题,在不更改编译器的情况下无法解决这些问题。

另一种方法是使用集合初始值设定项而不是匿名类型。这看起来像这样:

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

这将使您能够:

  1. 通过为字符串参数指定类似“IEnumerable.Current”的内容来处理显式接口实现。
  2. 定义Members.Add,这样您就不需要在初始化程序中指定委托类型。
  3. 你需要做一些事情来实现这个:

    1. 为C#类型名称编写一个小解析器。这只需要“。”,“[]”,“&lt;&gt;”,ID和原始类型名称,所以你可能会在几个小时内完成。
    2. 实施缓存,以便您只为每个唯一界面生成一个类
    3. 实现Reflection.Emit代码gen。这可能最多需要2天。

答案 1 :(得分:6)

它需要c#4,但开源框架impromptu interface可以在内部使用DLR代理来伪装这个开箱即用。虽然不如你提出的改变存在,但表现还是不错的。

using ImpromptuInterface.Dynamic;

...

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

答案 2 :(得分:4)

除了具有只读属性外,不能使匿名类型执行任何操作。

引用C# Programming Guide (Anonymous Types)

  

“匿名类型是类类型   由一个或多个公众组成   只读属性。没有其他种类   类成员如方法或   事件是允许的。匿名类型   无法转换为任何界面或   除了对象之外的类型。“

答案 3 :(得分:2)

只要我们提出一个接口愿望清单,我真的希望能够告诉编译器一个类在类定义之外实现一个接口 - 即使是在一个单独的程序集中。

例如,假设我正在开发一个从不同存档格式中提取文件的程序。我希望能够从不同的库中提取现有的实现 - 比如,SharpZipLib和商业PGP实现 - 并使用相同的代码使用这两个库而无需创建新类。然后我可以在通用约束中使用来自任一源的类型,例如。

另一种用法是告诉编译器System.Xml.Serialization.XmlSerializer实现了System.Runtime.Serialization.IFormatter接口(它已经做了,但是编译器不知道它)。

这也可用于实现您的请求,而不是自动执行。您仍然必须明确告诉编译器。不确定语法的外观,因为你仍然需要在某处手动映射方法和属性,这意味着大量的措辞。也许类似于扩展方法。

答案 4 :(得分:1)

你可以在Java中使用anonymous classes

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(); 
} 

答案 5 :(得分:1)

这不是很酷。内联匿名类:

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();
    }
})

答案 6 :(得分:1)

我要在这里抛弃它。我刚才写了它,但是IIRC它运作正常。

首先是一个帮助函数,用于获取MethodInfo并返回匹配TypeFunc的{​​{1}}。不幸的是,你需要为每个参数提供一个分支,而且我显然已经停在了三个。

Action

现在是将接口作为泛型参数并返回实现接口的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"); } } 并且具有构造函数(需要通过Type调用)并取Activator.CreateInstance的方法或每个方法/ getter / setter的Func。但是,您需要知道将它们放在构造函数中的正确顺序。或者(注释掉的代码)它可以生成一个DLL,然后您可以引用它并直接使用该类型。

Action

您可以将其用作例如。

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;
}

答案 7 :(得分:0)

有趣的想法,我有点担心,即使可以做到,也可能会让人感到困惑。例如。在使用非平凡的setter和getter定义属性时,或者如果声明类型还包含一个名为Foo的属性,如何消除Foo的歧义。

我想知道在更动态的语言中,或者使用C#4.0中的动态类型和DLR,这会更容易吗?

也许今天在C#中,一些意图可以通过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;}
}

答案 8 :(得分:-1)

目前这是不可能的。

这有什么区别,只是让IFoo成为一个具体的类?似乎这可能是更好的选择。

需要什么?一个新的编译器和大量的检查,以确保它们没有打破其他功能。就个人而言,我认为要求开发人员创建他们类的具体版本会更容易。

答案 9 :(得分:-1)

我通过“new IFoo(){...}”sintax在Java中使用了Amonimous类,当你​​必须快速实现一个简单的接口时,它很实用。

作为一个示例,以这种方式在传统对象上实现IDisposable会很好,只需一次,而不是派生一个新类来实现它。