通过反射调用本地通用方法

时间:2019-07-16 04:12:15

标签: c# reflection

我需要通过反射调用本地通用方法,但是我不知道该怎么做。

我的核心问题是我正在使用反射从一组查询中填充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数据结构。

1 个答案:

答案 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 });
}

DEMO

我认为不需要使用此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" }));

如果需要,可以为方法FetchStore创建通用重载,这些方法使用typeof(T)来调用所示的实现。我在示例实现中用optional标记了此方法。