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

我想知道做这样的工作需要什么:

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<j__TPar> : IFoo { readonly j__TPar i__Field; public f__AnonymousType0(j__TPar Foo) { this.i__Field = Foo; } public j__TPar Foo { get { return this.i__Field; } } public void Print() { Console.WriteLine(this.Foo); } } 

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

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

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

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

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

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

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

“创建”的声明如下所示:

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

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

但是,这种语法确实存在显式接口实现和重载成员的问题,如果不更改编译器就无法解决。

另一种方法是使用集合初始化程序而不是匿名类型。 这看起来像这样:

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

这将使您能够:

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

你需要做一些事情来实现这个:

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

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

 using ImpromptuInterface.Dynamic; 

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

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

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

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

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

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

另一个用途是告诉编译器System.Xml.Serialization.XmlSerializer实现System.Runtime.Serialization.IFormatter接口(它已经存在,但编译器不知道它)。

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

您可以使用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(); } 

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

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

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

首先是一个辅助函数,用于获取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"); } } 

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

 static Type GenerateInterfaceImplementation() { 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 instance = (ITest)Activator.CreateInstance(it, new Action(() => {Console.WriteLine("M1 called"); return;}), new Func((i, s) => "M2 gives " + s + i.ToString()), new Func(() => "prop value"), new Action(s => {Console.WriteLine("prop set to " + s);}), new Action(eh => {Console.WriteLine(eh("handler added"));}), new Action(eh => {Console.WriteLine(eh("handler removed"));})); // or with the generated DLL ITest instance = new DynamicITestWrapper( // parameters as before but you can see the signature ); 

有趣的想法,我有点担心,即使它可以做到它可能会让人感到困惑。 例如,在使用非平凡的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;} } 

目前这是不可能的。

这与简单地将IFoo变成具体的课程有什么区别呢? 似乎这可能是更好的选择。

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

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

作为示例,以这种方式在仅使用一次的遗留对象上实现IDisposable而不是派生新类来实现它将是很好的。