我需要通过反射调用本地通用方法,但是我不知道该怎么做。
我的核心问题是我正在使用反射从一组查询中填充POCO对象,这些查询从MySQL数据库中读取EAV数据。我很高兴展示此代码,但是它又冗长又复杂。我创建了一个简化的示例,我认为它很好地总结了问题。
考虑以下代码:
void Main()
{
var repository = new Repository();
repository.Store<Foo>(xs => xs.Count());
int Compute<M>(string[] source) => repository.Fetch<M>().Invoke(source);
Console.WriteLine(Compute<Foo>(new[] { "A", "B" }));
}
public class Foo { }
public class Repository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store<T>(Func<string[], int> value)
{
_store[typeof(T)] = value;
}
public Func<string[], int> Fetch<T>()
{
return (Func<string[], int>)_store[typeof(T)];
}
}
我有一个Repository
类型,可以存储由Func<string[], int>
索引的Type
。
在我的Main
方法中,我实例化了一个Repository
的实例,并针对类型xs => xs.Count()
存储了Foo
。
我有一个本地通用方法Compute
,当我在编译时知道M
的类型时,可以轻松地调用它。调用Compute<Foo>(new[] { "A", "B" })
计算出2
。没关系。
当我在运行时只知道M
时,就会发生此问题。
比方说我有这种类型:
public class Bar
{
public Foo Value;
}
现在我在Main
中的代码如下:
void Main()
{
var repository = new Repository();
repository.Store<Foo>(xs => xs.Count());
int Compute<M>(string[] source) => repository.Fetch<M>().Invoke(source);
var runtimeType = typeof(Bar).GetField("Value").FieldType;
var reflectedMethod = /* What Goes Here ??? */
Console.WriteLine(reflectedMethod.Invoke(new[] { "A", "B" }));
}
我对/* What Goes Here ??? */
部分深感困惑。
我发现了类似的question,但是该方法没有处理本地的泛型方法。
有人可以在运行时帮助调用这种方法吗?
这是完整的工作代码。它在带有NuGet“ System.Interactive”的LINQPad中运行,并且还有Maybe monad库,并引用了我的MySQL数据库。
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
internal class MagentoAttribute : Attribute
{
public string AttributeCode { get; private set; }
public MagentoAttribute(string code)
{
this.AttributeCode = code;
}
}
public static class Magento
{
public abstract class ProductBase
{
public uint EntityId { get; private set; }
public string Sku { get; private set; }
public decimal? Quantity { get; private set; }
}
public static T[] GetProducts<T>(this UserQuery @this) where T : ProductBase, new()
{
var setEntityId = typeof(ProductBase).GetProperty("EntityId", BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Public);
var setSku = typeof(ProductBase).GetProperty("Sku", BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Public);
var setQuantity = typeof(ProductBase).GetProperty("Quantity", BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Public);
var results =
(
from cpe in @this.catalog_product_entity
where cpe.sku != null && !cpe.sku.StartsWith("SHIP")
join csi in @this.cataloginventory_stock_item on cpe.entity_id equals csi.product_id
select new { cpe.entity_id, cpe.sku, csi.qty }
)
.ToArray()
.Select(x =>
{
var t = new T();
setEntityId.SetValue(t, x.entity_id);
setSku.SetValue(t, x.sku);
setQuantity.SetValue(t, x.qty);
return t;
})
.ToArray();
Func<string[], Dictionary<string, Func<uint, Maybe<string>>>> getStrings = attributeCodes =>
(
from eet in @this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in @this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in @this.catalog_product_entity_varchar on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.Concat(
from eet in @this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in @this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in @this.catalog_product_entity_text on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<string>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
Func<string[], Dictionary<string, Func<uint, Maybe<int?>>>> getIntegers = attributeCodes =>
(
from eet in @this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in @this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in @this.catalog_product_entity_int on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<int?>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
Func<string[], Dictionary<string, Func<uint, Maybe<DateTime?>>>> getDateTimes = attributeCodes =>
(
from eet in @this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in @this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in @this.catalog_product_entity_datetime on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<DateTime?>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
Func<string[], Dictionary<string, Func<uint, Maybe<decimal?>>>> getDecimals = attributeCodes =>
(
from eet in @this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in @this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in @this.catalog_product_entity_decimal on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<decimal?>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
var prerepo = new Prerepository();
prerepo.Store<Maybe<string>>(getStrings);
prerepo.Store<Maybe<int?>>(getIntegers);
prerepo.Store<Maybe<DateTime?>>(getDateTimes);
prerepo.Store<Maybe<decimal?>>(getDecimals);
var collapse = new Dictionary<Type, Delegate>()
{
{ typeof(Maybe<int?>), (Func<Maybe<int?>, Maybe<int?>>)(m => m) },
{ typeof(Maybe<int>), (Func<Maybe<int?>, Maybe<int>>)(m => (!m.HasValue || (m.HasValue && m.Value == null)) ? Maybe<int>.Nothing : m.Value.Value.ToMaybe()) },
{ typeof(int?), (Func<Maybe<int?>, int?>)(m => m.HasValue ? m.Value : (int?)null) },
{ typeof(int), (Func<Maybe<int?>, int>)(m => (m.HasValue && m.Value != null) ? m.Value.Value : default(int)) },
{ typeof(Maybe<decimal?>), (Func<Maybe<decimal?>, Maybe<decimal?>>)(m => m) },
{ typeof(Maybe<decimal>), (Func<Maybe<decimal?>, Maybe<decimal>>)(m => (!m.HasValue || (m.HasValue && m.Value == null)) ? Maybe<decimal>.Nothing : m.Value.Value.ToMaybe()) },
{ typeof(decimal?), (Func<Maybe<decimal?>, decimal?>)(m => m.HasValue ? m.Value : (decimal?)null) },
{ typeof(decimal), (Func<Maybe<decimal?>, decimal>)(m => (m.HasValue && m.Value != null) ? m.Value.Value : default(decimal)) },
{ typeof(Maybe<DateTime?>), (Func<Maybe<DateTime?>, Maybe<DateTime?>>)(m => m) },
{ typeof(Maybe<DateTime>), (Func<Maybe<DateTime?>, Maybe<DateTime>>)(m => (!m.HasValue || (m.HasValue && m.Value == null)) ? Maybe<DateTime>.Nothing : m.Value.Value.ToMaybe()) },
{ typeof(DateTime?), (Func<Maybe<DateTime?>, DateTime?>)(m => m.HasValue ? m.Value : (DateTime?)null) },
{ typeof(Maybe<string>), (Func<Maybe<string>, Maybe<string>>)(m => m) },
{ typeof(string), (Func<Maybe<string>, string>)(m => m.HasValue ? m.Value : default(string)) },
};
var attributes =
Enumerable
.Concat(
typeof(T)
.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(x => new
{
Field = x,
Attribute = x.GetCustomAttribute<MagentoAttribute>(),
})
.Where(x => x.Attribute != null)
.Select(x => new
{
Inject = (Action<object, object>)((o, v) => x.Field.SetValue(o, v)),
x.Attribute.AttributeCode,
AttributeType = x.Field.FieldType
}),
typeof(T)
.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(x => new
{
Property = x,
Attribute = x.GetCustomAttribute<MagentoAttribute>(),
})
.Where(x => x.Attribute != null)
.Select(x => new
{
Inject = (Action<object, object>)((o, v) => x.Property.SetValue(o, v)),
x.Attribute.AttributeCode,
AttributeType = x.Property.PropertyType
}))
.Where(x => collapse.ContainsKey(x.AttributeType))
.ToArray();
var postrepo = new Postrepository();
postrepo.Store<Maybe<int?>>(null);
void Fetch<M>(string[] attributeCodes) => postrepo.Store<M>(prerepo.Fetch<M>()(attributeCodes));
void InvokeHelper(Action<string[]> prototype, Type type, object data)
{
var method = prototype.Method;
var genericMethod = method.GetGenericMethodDefinition();
var concreteMethod = genericMethod.MakeGenericMethod(type);
concreteMethod.Invoke(prototype.Target, new[] { data });
}
Type lift(Type t)
{
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Maybe<>))
{
t = t.GetGenericArguments()[0];
}
if (!t.IsGenericType)
{
if (t.IsValueType)
{
t = typeof(Nullable<>).MakeGenericType(t);
}
}
t = typeof(Maybe<>).MakeGenericType(t);
return t;
}
(
from a in attributes
let m = lift(a.AttributeType)
group a.AttributeCode by m
)
.ForEach(x => InvokeHelper(Fetch<object>, x.Key, x.Distinct().ToArray()));
void Inject<M, P>(string attributeCode, Action<object, object> inject)
{
var b = postrepo.Fetch<M>();
var d = b[attributeCode];
var fmp = (Func<M, P>)collapse[typeof(P)];
results
.ForEach(r =>
{
var m = d(r.EntityId);
inject(r, fmp(m));
});
}
void InvokeHelper2(Action<string, Action<object, object>> prototype, Type[] types, object[] data)
{
var method = prototype.Method;
var genericMethod = method.GetGenericMethodDefinition();
var concreteMethod = genericMethod.MakeGenericMethod(types);
concreteMethod.Invoke(prototype.Target, data);
}
(
from a in attributes
let m = lift(a.AttributeType)
group new { a.AttributeType, a.Inject } by new { a.AttributeCode, m }
)
.ForEach(xs =>
{
xs
.ForEach(y =>
{
InvokeHelper2(Inject<object, object>, new[] { xs.Key.m, y.AttributeType }, new object[] { xs.Key.AttributeCode, y.Inject });
});
});
return results;
}
public class Prerepository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store<T>(Func<string[], Dictionary<string, Func<uint, T>>> value)
{
_store[typeof(T)] = value;
}
public Func<string[], Dictionary<string, Func<uint, T>>> Fetch<T>()
{
return (Func<string[], Dictionary<string, Func<uint, T>>>)_store[typeof(T)];
}
}
public class Postrepository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store<T>(Dictionary<string, Func<uint, T>> value)
{
_store[typeof(T)] = value;
}
public Dictionary<string, Func<uint, T>> Fetch<T>()
{
return (Dictionary<string, Func<uint, T>>)_store[typeof(T)];
}
}
}
现在我可以编写以下代码:
public class Product : Magento.ProductBase
{
[Magento("name")] public string Name { get; private set; }
[Magento("is_deleted")] private Maybe<int?> _is_deleted = Maybe<int?>.Nothing;
public bool IsDeleted { get => _is_deleted.HasValue ? _is_deleted.Value == 1 : false; }
[Magento("brand")] private Maybe<string> _brand { get; set; } = Maybe<string>.Nothing;
public string Brand { get => _brand.HasValue ? _brand.Value : "(missing)"; }
[Magento("cost")] public decimal Cost { get; private set; }
}
然后这个:
var ps =
Magento
.GetProducts<Product>(this)
.Where(x => x.Cost == 0m)
.Where(x => !x.IsDeleted)
.Where(x => x.Quantity > 0m);
现在我有了一种强类型的方法来读取Magento EAV数据结构。
答案 0 :(得分:3)
Jon Skeets answer也可以解决您的问题。
我只更改了签名以符合您的要求:
var repository = new Repository();
repository.Store<Foo>(xs => xs.Count());
int Compute<M>(string[] source) => repository.Fetch<M>().Invoke(source);
// Call Compute<M>
var runtimeKnownTime = typeof(Foo);
var computeResult = InvokeHelper(Compute<object>, new[] { "A", "B" }, runtimeKnownTime);
Console.WriteLine(computeResult);
这使用了InvokeHelper
的以下实现:
// This code was created by Jon Skeet : https://stackoverflow.com/a/43349127/2729609
// I only changed Action<int> to Func<int, int> and changed the return type.
private static int InvokeHelper(Func<string[], int> int32Func, object data, Type type)
{
// You probably want to validate that it really is a generic method...
var method = int32Func.Method;
var genericMethod = method.GetGenericMethodDefinition();
var concreteMethod = genericMethod.MakeGenericMethod(type);
return (int)concreteMethod.Invoke(int32Func.Target, new[] { data });
}
但我认为不需要使用此hack。在这种情况下,您不需要通用类型。 将存储库更改为:
public class Repository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store(Type id, Func<string[], int> value)
{
_store[id] = value;
}
// optional
public void Store<T>(Func<string[], int> value) => this.Store(typeof(T), value);
public Func<string[], int> Fetch(Type id)
{
return (Func<string[], int>)_store[id];
}
// optional
public Func<string[], int> Fetch<T>() => this.Fetch(typeof(T));
}
您可以在没有泛型的情况下使用它:
var repository = new Repository();
repository.Store(typeof(Foo), xs => xs.Count());
int Compute(Type id, string[] source) => repository.Fetch(id).Invoke(source);
// Call Compute<M>
var runtimeKnownTime = typeof(Foo);
Console.WriteLine(Compute(runtimeKnownTime, new[] { "A", "B" }));
如果需要,可以为方法Fetch
和Store
创建通用重载,这些方法使用typeof(T)
来调用所示的实现。我在示例实现中用optional
标记了此方法。