Запрос функции C #: реализовать интерфейсы для анонимных типов - PullRequest
14 голосов
/ 03 февраля 2009

Мне интересно, что нужно, чтобы сделать что-то вроде этой работы:

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 ]

9 голосов
/ 04 февраля 2009

Было бы несколько проблем с перегруженными членами, индексаторами и явными реализациями интерфейса.

Однако вы, вероятно, могли бы определить синтаксис таким образом, чтобы разрешить эти проблемы.

Интересно, что вы можете довольно близко приблизиться к тому, что хотите, в 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. Определение членов. Добавьте, чтобы вам не нужно было указывать тип делегата в инициализаторе.

Вам потребуется сделать несколько вещей, чтобы реализовать это:

  1. Writer небольшой парсер для имен типов C #. Это требует только ".", "[]", "<>", ID и имен примитивных типов, так что вы можете сделать это за несколько часов
  2. Реализация кеша, чтобы вы генерировали только один класс для каждого уникального интерфейса
  3. Реализация кода Reflection.Emit gen. Это может занять максимум 2 дня.
6 голосов
/ 08 марта 2011

Требуется c # 4, но фреймворк с открытым исходным кодом импровизированный интерфейс может подделать это из коробки, используя прокси-серверы DLR внутри. Производительность хорошая, но не такая хорошая, как если бы предложенное вами изменение существовало.

using ImpromptuInterface.Dynamic;

...

var f = ImpromptuGet.Create<IFoo>(new{ 
                Foo = "foo",
                Print = ReturnVoid.Arguments(() => Console.WriteLine(Foo))
            });
4 голосов
/ 04 февраля 2009

Анонимный тип нельзя заставить ничего делать, кроме как иметь свойства только для чтения.

Цитирование C # Руководство по программированию (анонимные типы) :

"Анонимные типы - это типы классов, которые состоят из одного или нескольких публичных свойства только для чтения. Других видов нет членов класса, таких как методы или события разрешены. Анонимный тип не может быть приведен к любому интерфейсу или тип за исключением объекта. "

2 голосов
/ 04 февраля 2009

Пока мы публикуем список пожеланий интерфейса, я бы действительно хотел сказать компилятору, что класс реализует интерфейс вне определения класса - даже в отдельной сборке.

Например, допустим, я работаю над программой для извлечения файлов из разных форматов архивов. Я хочу иметь возможность использовать существующие реализации из разных библиотек & mdash; скажем, SharpZipLib и коммерческая реализация PGP & mdash; и использовать обе библиотеки, используя один и тот же код, без создания новых классов. Тогда я мог бы использовать типы из любого источника в общих ограничениях, например.

Другое использование будет сообщать компилятору, что System.Xml.Serialization.XmlSerializer реализует интерфейс System.Runtime.Serialization.IFormatter (он уже делает, но компилятор не знает).

Это также может быть использовано для реализации вашего запроса, но не автоматически. Вам все равно придется явно сообщить об этом компилятору. Не уверен, как будет выглядеть синтаксис, потому что вам все равно придется где-то вручную сопоставлять методы и свойства, что означает много словоблудия. Может быть, что-то похожее на методы расширения.

1 голос
/ 14 ноября 2012

Я собираюсь сбросить это здесь. Я написал это некоторое время назад, но IIRC работает нормально.

Сначала вспомогательная функция берет 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");
    }
}

А теперь метод, который принимает интерфейс в качестве универсального параметра и возвращает Type, который реализует интерфейс и имеет конструктор (должен вызываться через Activator.CreateInstance), принимая Func или Action для каждого метод / получатель / установщик. Вы должны знать правильный порядок, чтобы поместить их в конструктор. В качестве альтернативы (закомментированный код) он может сгенерировать 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 голос
/ 28 декабря 2011

Разве это не круто. Встроенный анонимный класс:

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 голос
/ 26 октября 2010

Вы можете иметь что-то вроде анонимных классов в 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(); 
} 
0 голосов
/ 01 февраля 2011

Я использовал в Java класс Amonimous через синтаксис "new IFoo () {...}", и это практично и легко, когда вам нужно быстро реализовать простой интерфейс.

В качестве примера было бы неплохо реализовать IDisposable таким образом на устаревшем объекте, который используется только один раз вместо создания нового класса для его реализации.

0 голосов
/ 04 февраля 2009

Интересная идея, я был бы немного обеспокоен тем, что даже если бы это можно было сделать, это могло бы сбить с толку. Например. при определении свойства с нетривиальными установщиками и получателями, или как устранить неоднозначность Foo, если объявленный тип также содержит свойство с именем Foo.

Интересно, будет ли это проще в более динамичном языке или даже с динамическим типом и DLR в C # 4.0?

Возможно, сегодня в 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;}
}
0 голосов
/ 04 февраля 2009

Это не было бы возможно в настоящее время.

Какая разница между этим и простым превращением IFoo в конкретный класс? Похоже, что это может быть лучшим вариантом.

Что это займет? Новый компилятор и множество проверок, чтобы убедиться, что они не сломали другие функции. Лично я думаю, что было бы проще потребовать от разработчиков создать конкретную версию своего класса.

...