Как использовать выражение для построения анонимного типа?
В С# 3.0 вы можете использовать выражение для создания класса со следующим синтаксисом:
var exp = Expression.New(typeof(MyClass));
var lambda = LambdaExpression.Lambda(exp);
object myObj = lambda.Compile().DynamicInvoke();
Но как вы используете Expression для создания анонимного класса?
//anonymousType = typeof(new{ Name="abc", Num=123});
Type anonymousType = Expression.NewAnonymousType??? <--How to do ?
var exp = Expression.New(anonymousType);
var lambda = LambdaExpression.Lambda(exp);
object myObj = lambda.Compile().DynamicInvoke();
Ответы
Ответ 1
Вы близки, но вам нужно знать, что анонимные типы не имеют конструкторов по умолчанию. Следующий код печатает { Name = def, Num = 456 }
:
Type anonType = new { Name = "abc", Num = 123 }.GetType();
var exp = Expression.New(
anonType.GetConstructor(new[] { typeof(string), typeof(int) }),
Expression.Constant("def"),
Expression.Constant(456));
var lambda = LambdaExpression.Lambda(exp);
object myObj = lambda.Compile().DynamicInvoke();
Console.WriteLine(myObj);
Если вам не нужно создавать много экземпляров этого типа, Activator.CreateInstance
будет делать то же самое (он быстрее для нескольких экземпляров, но медленнее для многих). Этот код печатает { Name = ghi, Num = 789 }
:
Type anonType = new { Name = "abc", Num = 123 }.GetType();
object myObj = Activator.CreateInstance(anonType, "ghi", 789);
Console.WriteLine(myObj);
Ответ 2
Так как анонимный тип не имеет пустого конструктора по умолчанию, вы не можете использовать перегрузку Expression.New(Type)
... вы должны предоставить ConstructorInfo
и параметры методу Expression.New
. Чтобы сделать это, вы должны иметь возможность получить тип... так что вам нужно создать экземпляр "заглушки" анонимного типа и использовать его для получения Type
и ConstructorInfo
, а затем передать параметры для метода Expression.New
.
Вот так:
var exp = Expression.New(new { Name = "", Num = 0 }.GetType().GetConstructors()[0],
Expression.Constant("abc", typeof(string)),
Expression.Constant(123, typeof(int)));
var lambda = LambdaExpression.Lambda(exp);
object myObj = lambda.Compile().DynamicInvoke();
Ответ 3
Вы можете избежать использования DynamicInvoke
, которое очень медленно. Вы можете использовать вывод типа в С#, чтобы получить ваш анонимный тип, созданный в общем виде. Что-то вроде:
public static Func<object[], T> AnonymousInstantiator<T>(T example)
{
var ctor = typeof(T).GetConstructors().First();
var paramExpr = Expression.Parameter(typeof(object[]));
return Expression.Lambda<Func<object[], T>>
(
Expression.New
(
ctor,
ctor.GetParameters().Select
(
(x, i) => Expression.Convert
(
Expression.ArrayIndex(paramExpr, Expression.Constant(i)),
x.ParameterType
)
)
), paramExpr).Compile();
}
Теперь вы можете позвонить,
var instantiator = AnonymousInstantiator(new { Name = default(string), Num = default(int) });
var a1 = instantiator(new object[] { "abc", 123 }); // strongly typed
var a2 = instantiator(new object[] { "xyz", 789 }); // strongly typed
// etc.
Вы можете использовать метод AnonymousInstantiator
для генерации функций для создания какого-либо анонимного типа с любым количеством свойств, просто чтобы вы сначала передали соответствующий пример. Входные параметры должны передаваться как массив объектов. Если вы беспокоитесь о производительности бокса, тогда вам нужно написать собственный экземпляр, который принимает только string
и int
в качестве входных параметров, но использование такого экземпляра будет немного более ограниченным.