Запрос функции С#: реализовать интерфейсы на анонимных типах
Мне интересно, что нужно сделать, чтобы сделать что-то вроде этой работы:
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);
}
}
Есть ли причина, по которой компилятор не сможет сделать что-то подобное? Даже для непустых методов или методов, которые принимают параметры, компилятор должен иметь возможность выводить типы из декларации интерфейса.
Отказ от ответственности: Пока я понимаю, что в настоящее время это невозможно, и было бы разумнее просто создать конкретный класс в этом случае. Меня больше интересуют теоретические аспекты этого.
Ответы
Ответ 1
Было бы несколько проблем с перегруженными членами, индексаторами и явными реализациями интерфейса.
Однако вы могли бы определить синтаксис таким образом, чтобы разрешить эти проблемы.
Интересно, что вы можете приблизиться к тому, что хотите с С# 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"}
}
}
Это позволит вам:
- Обработать явную реализацию интерфейса, указав что-то вроде "IEnumerable.Current" для строкового параметра.
- Определить Members.Add, чтобы вам не нужно указывать тип делегата в инициализаторе.
Вам нужно сделать несколько вещей, чтобы реализовать это:
- Writer - небольшой парсер для имен типов С#. Для этого требуется только ".", "[]", "< > ", ID и имена примитивных типов, поэтому вы могли бы сделать это через несколько часов.
- Реализовать кеш, чтобы вы генерировали только один класс для каждого уникального интерфейса
- Внедрить код Reflection.Emit. Это, вероятно, займет не более 2 дней.
Ответ 2
Для этого требуется С# 4, но интерфейс импровизированного интерфейса может подделать его из коробки, используя внутренние прокси DLR. Производительность хороша, хотя и не так хороша, как если бы предложенное вами изменение существовало.
using ImpromptuInterface.Dynamic;
...
var f = ImpromptuGet.Create<IFoo>(new{
Foo = "foo",
Print = ReturnVoid.Arguments(() => Console.WriteLine(Foo))
});
Ответ 3
Анонимный тип не может быть сделан, чтобы делать что-либо, кроме как иметь свойства только для чтения.
Цитирование Руководство по программированию на С# (анонимные типы):
"Анонимные типы - это типы классов, которые состоят из одного или нескольких публичных только для чтения. Никаких других видов членов класса, таких как методы или события разрешены. Анонимный тип не могут быть добавлены к любому интерфейсу или тип, за исключением объекта."
Ответ 4
Пока мы собираем список желаний интерфейса, мне бы очень хотелось сообщить компилятору, что класс реализует интерфейс вне определения класса - даже в отдельной сборке.
Например, скажем, я работаю над программой для извлечения файлов из разных архивных форматов. Я хочу, чтобы иметь возможность использовать существующие реализации из разных библиотек — скажем, SharpZipLib и коммерческая реализация PGP — и потребляют обе библиотеки, используя один и тот же код, без создания новых классов. Тогда я мог бы использовать типы из любого источника в общих ограничениях, например.
Другим использованием было бы сказать компилятору, что System.Xml.Serialization.XmlSerializer
реализует интерфейс System.Runtime.Serialization.IFormatter
(он уже делает, но компилятор этого не знает).
Это может быть использовано для реализации вашего запроса, а не автоматически. Вам все равно придется явно сообщать компилятору об этом. Не знаете, как будет выглядеть синтаксис, потому что вам все равно придется вручную сопоставлять методы и свойства, что означает много словесности. Возможно, что-то похожее на методы расширения.
Ответ 5
У вас может быть что-то вроде анонимных классов в 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
Разве это не было бы круто. Inline анонимный класс:
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
Я собираюсь свалить это здесь. Я написал это некоторое время назад, но 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
);
Ответ 8
Интересная идея, я был бы немного обеспокоен тем, что даже если это можно было бы сделать, это может запутать. Например. при определении свойства с нетривиальными сеттерами и геттерами, или как устранить ошибки Foo, если тип объявления также содержит свойство Foo.
Интересно, будет ли это проще в более динамичном языке или даже с динамическим типом и DLR в С# 4.0?
Возможно, сегодня на С# некоторые из намерений могут быть достигнуты с помощью 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;}
}
Ответ 9
В настоящее время это невозможно.
Какая разница между этим и просто созданием IFoo конкретного класса? Похоже, это лучший вариант.
Что нужно? Новый компилятор и тонны проверок, чтобы они не нарушили другие функции. Лично я считаю, что проще просто потребовать от разработчиков просто создать конкретную версию своего класса.
Ответ 10
Я использовал в Java класс Amonimous через "новый IFoo() {...}" sintax, и это практично и легко, когда вам нужно быстро реализовать простой интерфейс.
В качестве примера было бы неплохо реализовать IDisposable таким образом на устаревшем объекте, который использовался только один раз, вместо того, чтобы выводить новый класс для его реализации.