Как вы уже поняли, вы не можете просто использовать var xNew = Expression.New(typeof(object));
вместо var xNew = Expression.New(typeof(SystemViewModel));
, поскольку оно выдает исключение:
System.ArgumentException: '' Division 'не является членом типа' System.Object ''
Итак, для меня решение состоит в том, чтобы создать анонимный тип самостоятельно. Чтобы создать тип во время выполнения, вам нужно создать динамическую сборку с динамическим модулем. Вам также придется подумать о кэшировании этих типов, иначе исключение нехватки памяти будет за углом.
Объявить Ленивый ModuleBuilder
Сначала мы объявим Lazy
статический ModuleBuilder
, который создаст динамический модуль как синглтон:
private static Lazy<ModuleBuilder> ModuleBuilder = new Lazy<ModuleBuilder>(() =>
AssemblyBuilder
.DefineDynamicAssembly(new AssemblyName("AnonymousTypesAssembly"), AssemblyBuilderAccess.Run)
.DefineDynamicModule("AnonymousTypesModule"));
Эта настройка гарантирует, что во время выполнения будет создана только одна динамическая сборка.
Объявить метод создания анонимного типа
private static Type CreateAnonymousType(IEnumerable<PropertyInfo> propertyInfos)
{
var moduleBuilder = ModuleBuilder.Value;
var typeName = Guid.NewGuid().ToString(); // Give the new type a random name
var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);
foreach (var propertyInfo in propertyInfos)
typeBuilder.DefineField(propertyInfo.Name, propertyInfo.PropertyType, FieldAttributes.Public);
return typeBuilder.CreateType();
}
Изменение CreateNewStatement
Для гибкости я сделал этот метод универсальным и позволил вам передавать имена полей в виде набора (мы не хотим удваивать).
private static Func<TOriginal, object> CreateNewStatementFor<TOriginal>(ISet<string> fields)
{
// input parameter "o"
var xParameter = Expression.Parameter(typeof(TOriginal), "o");
var propertyInfos = fields
.Select(propertyName => typeof(TOriginal).GetProperty(propertyName))
.ToArray();
var anonymousType = CreateAnonymousType(propertyInfos);
// create initializers
var bindings = propertyInfos
.Select(mi =>
{
// mi == property "Field1"
// original value "o.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = o.Field1"
var mo = anonymousType.GetField(mi.Name);
return Expression.Bind(mo, xOriginal);
})
.ToArray();
// new statement "new Data()"
var xNew = Expression.New(anonymousType);
// initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<TOriginal, object>>(xInit, xParameter);
//LambdaExpression.
// compile to Func<Data, Data>
return lambda.Compile();
}
Кэширование
Теперь, каждый раз, когда вы вызываете CreateNewStatementFor
, он создает новый динамический тип с новым случайным именем. Даже при создании заявления для тех же свойств. Я не должен упоминать, что это плохо и приведет к утечкам памяти. Чтобы исправить это, мы добавим поточно-ориентированный механизм кэширования, в котором ключ основан на исходном типе и его выбранных свойствах упорядочены по возрастанию.
private static ConcurrentDictionary<string, object> StatementFuncCache = new ConcurrentDictionary<string, object>();
public static Func<TOriginal, object> GetOrCreateNewStatementFor<TOriginal>(ISet<string> fields)
{
var key = $"{typeof(TOriginal).Name} {string.Join(",", fields.OrderBy(x => x))}";
var func = StatementFuncCache.GetOrAdd(key, _ => CreateNewStatementFor<TOriginal>(fields));
return (Func<TOriginal, object>)func;
}
Использование
var result2 = dte.Select(GetOrCreateNewStatementFor<SystemViewModel>(new HashSet<string> { "Division", "Department" })).ToList();