代码之家  ›  专栏  ›  技术社区  ›  Andrew Hare

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

  •  13
  • Andrew Hare  · 技术社区  · 16 年前

    我在想,要想让这样的东西发挥作用需要什么:

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

    有什么理由编译器不能做这样的事情吗?即使对于非空方法或采用参数的方法,编译器也应该能够从接口声明中推断类型。

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

    10 回复  |  直到 13 年前
        1
  •  8
  •   Scott Wisniewski    16 年前

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

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

    有趣的是,通过编写一个库,你可以非常接近你想要的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. 定义成员。添加以便不需要在初始值设定项中指定委托类型。

    您需要做一些事情来实现这一点:

    1. 为C类型名编写一个小的解析器。这只需要“.”、“[]”、“<>”、ID和基元类型名称,因此您可能需要几个小时就可以完成。
    2. 实现缓存,以便只为每个唯一接口生成一个类
    3. 实现反射。发出代码生成。这可能最多需要2天。
        2
  •  6
  •   jbtule    14 年前

    它需要C 4,但是OpenSource框架 impromptu interface 可以在内部使用DLR代理进行开箱即用的伪造。性能很好,尽管不如您提议的变更那样好。

    using ImpromptuInterface.Dynamic;
    

    var f = ImpromptuGet.Create<IFoo>(new{ 
                    Foo = "foo",
                    Print = ReturnVoid.Arguments(() => Console.WriteLine(Foo))
                });
    
        3
  •  4
  •   Christian C. Salvadó    16 年前

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

    引用 C# Programming Guide (Anonymous Types) :

    “匿名类型是指 由一个或多个公众组成 只读属性。没有其他种类 类成员,如方法或 允许事件。匿名类型 不能强制转换到任何接口或 类型,对象除外。“

        4
  •  2
  •   Joel Coehoorn    16 年前

    只要我们列出一个接口愿望列表,我真的想告诉编译器一个类实现了类定义之外的接口——即使是在一个单独的程序集中。

    例如,假设我正在开发一个程序,从不同的存档格式中提取文件。我希望能够从不同的库(例如,sharpziplib和商业的pgp实现)中引入现有的实现,并且在不创建新类的情况下使用相同的代码来使用这两个库。例如,我可以在泛型约束中使用来自任意一个源的类型。

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

    这也可以用来实现您的请求,只是不能自动实现。您仍然需要明确地告诉编译器关于它的信息。不确定语法的外观,因为您仍然需要手动将方法和属性映射到某个地方,这意味着需要大量的措辞。可能和扩展方法类似。

        5
  •  1
  •   Jordão    15 年前

    你可以吃点类似的东西 anonymous classes 在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(); 
    } 
    
        6
  •  1
  •   Tomislav Markovski    13 年前

    这不是很酷吗?内联匿名类:

    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();
        }
    })
    
        7
  •  1
  •   Rawling isekaijin    13 年前

    我要把这个扔到这里。我刚才写的,但是我觉得它可以用。

    首先是一个助手函数 MethodInfo 并返回 Type 匹配的 Func 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");
        }
    }
    

    现在,该方法将一个接口作为泛型参数,并返回 类型 实现接口并有一个构造函数(需要通过 Activator.CreateInstance 以A 芬克 行动 对于每个方法/getter/setter。不过,您需要知道将它们放入构造函数的正确顺序。或者(注释掉的代码)它可以生成一个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
        );
    
        8
  •  0
  •   codybartfast    16 年前

    有趣的想法,我会有点担心,即使可以做到,它可能会变得混乱。例如,在定义具有重要setter和getter的属性时,或者在声明类型还包含名为foo的属性时如何消除foo的歧义。

    我想知道,在更动态的语言中,这是否更容易,甚至在C 4.0中使用动态类型和DLR?

    也许在今天的C部分意图可以通过兰姆达斯实现:

    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;}
    }
    
        9
  •  -1
  •   David Morton    16 年前

    这在目前是不可能的。

    这和简单地将ifoo变成一个具体的类有什么区别?似乎这是更好的选择。

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

        10
  •  -1
  •   maborg    14 年前

    我在Java中使用了“新iFoO()”{ }},这是一个非常有趣的类,当你必须快速实现一个简单的接口时,它是实用的和简单的。

    作为一个示例,在 遗产 刚刚使用的对象 一次 而不是派生一个新的类来实现它。