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

14

我想知道让类似这样的东西工作需要做什么:

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方法或需要参数的方法,编译器也应该能够从接口声明中推断出类型。

免责声明:虽然我意识到目前这是不可能的,而在此情况下创建具体类更为合理,但我更感兴趣的是其理论方面。


这个想法昨天突然冒出来了,我试着创建一个 Anonymous<T> 类并使用 Reflection.Emit 来构建一个实现接口 T 的类。但这种方法太过昂贵了!于是我在 Stack Overflow 上尝试了一下,发现了这个问题,你现在有什么进展了吗? - Cheng Chen
10个回答

9

对于重载成员、索引器和显式接口实现可能会有一些问题。

但是,你可以通过定义语法的方式来解决这些问题。

有趣的是,通过编写库,你可以在C# 3.0中实现接近你想要的功能。基本上,你可以这样做:

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

这与您想要的非常接近。主要区别在于需要调用“Create”而不是使用“new”关键字,并且需要指定委托类型。

“Create”的声明如下:

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,使您不需要在初始化器中指定委托类型。
您需要执行以下操作才能实现此目标:
  1. 编写一个小型的C#类型名称解析器。这仅需要使用“。”、“[]”、“<>”、“ID”和原始类型名称,因此您可能只需要几个小时即可完成。
  2. 实现缓存,以便每个唯一接口仅生成一个类。
  3. 实现Reflection.Emit代码生成。这可能最多需要两天时间。

6

这需要使用c# 4,但开源框架impromptu interface可以使用DLR代理来直接完成此操作。尽管性能不如您所提出的更改,但表现仍然良好。

using ImpromptuInterface.Dynamic;

...

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

4

匿名类型除了具有只读属性外,不能做任何其他事情。

引用C#编程指南(匿名类型)

“匿名类型是由一个或多个公共只读属性组成的类类型。不允许其他任何种类的类成员,例如方法或事件。匿名类型不能转换为除object以外的任何接口或类型。”


3
可以,但我希望修改那个定义 :) - Andrew Hare

2
只要我们提出接口愿望清单,我就希望能够告诉编译器一个类在类定义之外实现了一个接口-即使在另一个程序集中。
例如,假设我正在开发一个程序来从不同的存档格式中提取文件。我想能够从不同的库中引入现有的实现-比如SharpZipLib和商业PGP实现-并使用相同的代码消费这两个库,而不创建新的类。然后我可以在泛型约束中使用来自任一来源的类型,例如。
另一个用途是告诉编译器System.Xml.Serialization.XmlSerializer实现了System.Runtime.Serialization.IFormatter接口(它已经实现了,但编译器不知道)。
这也可以用于实现您的请求,只是不会自动执行。您仍然需要明确地告诉编译器。不确定语法会是什么样子,因为您仍然需要手动映射方法和属性到某个位置,这意味着会有很多冗长的话语。也许类似于扩展方法的东西。

“它已经实现了,但编译器不知道。”
  1. 不,它没有。
  2. 它实现了Serialize/Deserialize,但没有实现三个属性Binder、Context、SurrogateSelector。
- Szymon Rozga
嗯:你可以很容易地把它塞进去。它本应该被构建成这样,你可以有一个接受IFormatter实例的方法,并传递BinaryFormatter、SoapFormatter、现有的XmlSerializer或你自己的IFormatter实现,而不会出现问题。 - Joel Coehoorn
你知道 Oxygene 有这个功能吗?我也在这里描述了一个我想看到的特性。 - Jordão
ImpromptuInterface 可以在 c# 4 中实现此功能。 - jbtule

1

我要把这个放在这里。我一段时间前写的,但是如果我没记错,它应该可以正常工作。

首先是一个辅助函数,它接受一个 MethodInfo 并返回与之匹配的 FuncActionType。不幸的是,你需要为每个参数数量编写一个分支,而我显然只停留在了三个。

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

现在有一个方法,它以接口作为泛型参数,并返回实现该接口并具有构造函数(需要通过Activator.CreateInstance调用)的Type,每个方法/ getter/setter都需要使用FuncAction进行调用。但是您需要知道正确的顺序放置它们在构造函数中。或者(已注释的代码)它可以生成一个DLL,然后您可以引用并直接使用该类型。

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

您可以将此用作示例。

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

1
你可以在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
这不是很酷吗?内联匿名类:
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();
    }
})

0

有趣的想法,但我会有点担心即使能够实现,它也可能变得混乱。例如,在定义具有非平凡设置器和获取器的属性时,或者如何消除声明类型也包含名为Foo的属性时的歧义。

我不知道在更动态的语言中,或者甚至在C# 4.0中使用动态类型和DLR是否会更容易实现这一点?

也许今天在C#中可以通过使用lambda表达式来实现某些意图:

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

我在Java中使用了匿名类,通过“new IFoo(){...}”语法,当你需要快速实现一个简单的接口时,这是实用和容易的。

作为示例,如果要在仅使用一次的对象上实现IDisposable,以此方式实现它比派生一个新类来实现它更好。


-1

目前不可能做到这一点。

如果直接将IFoo定义为具体类,这与仅此的实现方法有何不同?似乎这可能是更好的选择。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接