【问题标题】:Unity - Resolve generic implementation that inherits from TUnity - 解决从 T 继承的通用实现
【发布时间】:2016-02-18 11:32:55
【问题描述】:

我正在使用 Unity,并希望解析所有具有可从特定类型分配的泛型类型的实现。

我目前是这样注册的:

container.RegisterType<IQueryGenerator<IPerson>, PersonQueryGenerator>("1");
container.RegisterType<IQueryGenerator<ICustomer>, CustomerQueryGenerator>("2");
container.RegisterType<IQueryGenerator<IEmployee>, EmployeeQueryGenerator>("3");
container.RegisterType<IQueryGenerator<IManager>, ManagerQueryGenerator>("4");

然后层次结构是这样的:

public interface IPerson
{
}

public interface ICustomer : IPerson
{
}

public interface IEmployee : IPerson
{
}

public interface IManager : IEmployee
{
}

以及我正在尝试解决的实现的接口:

public interface IQueryGenerator<T>
{
   IEnumerable<Expression<Func<T, bool>>>  GenerateQueries();
}

现在我希望能够解析所有 IQueryGenerators,其中泛型类型可分配给我拥有的类型。所以我有一个应该包装它的工厂:

    public class QueryFactory
    {
        private readonly IUnityContainer _container;

        public QueryFactory(IUnityContainer container)
        {
            _container = container;
        }

        public IEnumerable<Expression<Func<TModel, bool>>> GenerateQueries<TModel>()
        {
            // Get all querygenerators which can handle TModel.
            IQueryGenerator<TModel>[] queryGenerators = _container.ResolveAll<IQueryGenerator<TModel>>();
            foreach (var queryGenerator in queryGenerators)
            {
                // Do something with queryGenerator.
                var result = queryGenerator.GenerateQueries();
            }
        }
    }

我想要的是_container.ResolveAll&lt;IQueryGenerator&lt;IEmployee&gt;&gt;() 返回PersonQueryGeneratorEmployeeQueryGeneratorManagerQueryGenerator,因为IManagerIEmployeeIEmployeeIPerson

_container.ResolveAll&lt;IQueryGenerator&lt;ICustomer&gt;&gt;() 返回PersonQueryGeneratorCustomerQueryGenerator

IEmployee可以向下转换为IPerson,可以由PersonQueryGenerator处理。

如何注册 IQueryGenerators,以及如何根据TModel 解决它们?或者有没有其他方法可以根据接口TModel实现来检索QueryGenerators?

编辑:

我添加了我想要做的概念验证。它运行良好,但非常硬编码。

class Program
{
    static void Main(string[] args)
    {
        IEnumerable<IManager> managers = new List<IManager>
        {
            new Manager()
            {
                Birthday = DateTime.Now.AddDays(1),
                Name = "Executive Manager",
                EmployeeNumber = 9,
                IsExecutive = true,
                Salary = 1000
            },
            new Manager()
            {
                Birthday = DateTime.Now.AddDays(-1),
                Name = "Ordinary Manager",
                EmployeeNumber = 8,
                IsExecutive = false,
                Salary = 900
            },
        };
        var queries = GenerateQueries();

        managers = queries.Aggregate(managers, (current, expression) => current.Where(expression));
        foreach (var manager in managers)
        {
            Console.WriteLine("Manager: {0}", manager.Name);
        }
        Console.ReadKey();
    }

    // Change this to a generic method.
    static IEnumerable<Func<IManager, bool>> GenerateQueries()
    {
        // Change this to a more generic management
        IList<IQueryGenerator<IManager>> queryGenerators = new List<IQueryGenerator<IManager>>();
        if (typeof(IPerson).IsAssignableFrom(typeof(IManager)))
        {
            queryGenerators.Add(new PersonQueryGenerator());
        }
        if (typeof(IEmployee).IsAssignableFrom(typeof(IManager)))
        {
            queryGenerators.Add(new EmployeeGenerator());
        }
        if (typeof(IManager).IsAssignableFrom(typeof(IManager)))
        {
            queryGenerators.Add(new ManagerQueryGenerator());
        }

        // Fetch queries and return them.
        var queries = new List<Func<IManager, bool>>();
        foreach (var queryGenerator in queryGenerators)
        {
            // Do something with queryGenerator.
            queries.AddRange(queryGenerator.GenerateQueries());
        }
        return queries;
    }

    // I want to use this method instead.
    static IEnumerable<Func<TModel, bool>> GenerateQueries<TModel>()
    {
        // Do the same thing as the method above
        //TODO: Resolve all IQueryGenerators that can handle TModel.
        return null;
    }
}

public interface IQueryGenerator<in T>
{
    IEnumerable<Func<T, bool>> GenerateQueries();
}

public class ManagerQueryGenerator : IQueryGenerator<IManager>
{
    public IEnumerable<Func<IManager, bool>> GenerateQueries()
    {
        Console.WriteLine("ManagerQueryGenerator called");
        yield return x => x.IsExecutive;
    }
}

public class PersonQueryGenerator : IQueryGenerator<IPerson>
{
    public IEnumerable<Func<IPerson, bool>> GenerateQueries()
    {
        Console.WriteLine("PersonQueryGenerator called");
        yield return x => x.Birthday > DateTime.Now;
    }
}

public class EmployeeGenerator : IQueryGenerator<IEmployee>
{
    public IEnumerable<Func<IEmployee, bool>> GenerateQueries()
    {
        Console.WriteLine("EmployeeGenerator called");
        yield return x => x.Salary > 900;
    }
}

public interface IHaveName
{
    string Name { get; set; }
}

public interface IPerson : IHaveName
{
    DateTime Birthday { get; set; }
}

public interface ICustomer : IPerson
{
    int MoneyToSpend { get; set; }
}

public interface IEmployee : IPerson
{
    int EmployeeNumber { get; set; }
    int Salary { get; set; }
}

public interface IManager : IEmployee
{
    bool IsExecutive { get; set; }
}

public class Manager : IManager
{
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
    public int EmployeeNumber { get; set; }
    public int Salary { get; set; }
    public bool IsExecutive { get; set; }
}

输出是:

PersonQueryGenerator called
EmployeeGenerator called
ManagerQueryGenerator called
Manager: Executive Manager

这正是我想要的,但具有更通用的类型管理,我真的很想使用Expression&lt;Func&lt;T,bool&gt;&gt;,因为它将与 EF 一起使用。

public interface IQueryGenerator<T>
{
   IEnumerable<Expression<Func<T, bool>>>  GenerateQueries();
}

代替:

public interface IQueryGenerator<in T>
{
   IEnumerable<Func<T, bool>>  GenerateQueries();
}

【问题讨论】:

  • 但是queryGenerators 的类型是什么? object[]?
  • 但是IQueryGenerator&lt;IPerson&gt; 可以从IQueryGenerator&lt;IManager&gt; 分配吗?我不这么认为。
  • @YacoubMassad - 不,你完全正确。它们不能相互分配。但是,IPerson 可以从 IManager 分配,我希望 Unity 能够理解这一点。但也许不可能?
  • 我认为这行不通,因为类型不兼容。你将如何使用GenerateQueries 的返回值? result 的类型是什么?你会如何消费它?
  • @YacoubMassad - 我已经用运行的概念证明更新了这个问题。但是,它不使用通用。但它表明您可以为同一模型使用不同的 IQueryGenerator,只要该模型可分配给某个接口。

标签: c# generics unity-container


【解决方案1】:

当您填充统一容器时,可能需要一点点反射。

private static UnityContainer container = new UnityContainer();

static void RegisterGenerator<T, TG>() where TG : IQueryGenerator<T>
{
  var assembly = typeof(T).Assembly;
  // walk trough all interfaces in assembly, if the interface inherits from
  // typeof(T), add that particular IQueryGenerator<> to container as well
  foreach (var type in assembly.GetTypes())
  {
    if (typeof(T).IsAssignableFrom(type) && type.IsInterface)
    {
      var generatorType = typeof(IQueryGenerator<>).MakeGenericType(type);
      container.RegisterType(generatorType, typeof(TG), type.Name);
    }
  }
}

static IEnumerable<Func<T, bool>> GenerateQueries<T>() where T : IPerson
{
  var queryGenerators = container.ResolveAll<IQueryGenerator<T>>().ToList();

  // Fetch queries and return them.
  var queries = new List<Func<T, bool>>();
  foreach (var queryGenerator in queryGenerators)
  {
    // Do something with queryGenerator.
    queries.AddRange(queryGenerator.GenerateQueries());
  }
  return queries;
}

例子:

static void Main(string[] args)
{
  RegisterGenerator<IPerson, PersonQueryGenerator>();
  RegisterGenerator<IManager, ManagerQueryGenerator>();

  var managers = new List<IPerson>
  {
    new Manager
    {
      Birthday = DateTime.Now.AddDays(1),
      Name = "Executive Manager",
      EmployeeNumber = 9,
      IsExecutive = true,
      Salary = 1000
    },
    new Manager
    {
      Birthday = DateTime.Now.AddDays(-1),
      Name = "Ordinary Manager",
      EmployeeNumber = 8,
      IsExecutive = false,
      Salary = 900
    }
  }.AsEnumerable();
  var queries = GenerateQueries<IPerson>();

  managers = queries.Aggregate(managers, (current, expression) => current.Where(expression));
  foreach (var manager in managers)
  {
    Console.WriteLine("Manager: {0}", manager.Name);
  }
  Console.ReadKey();
}

【讨论】:

    【解决方案2】:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Unity;
    
    namespace ConsoleApplication1
    {
        internal class Program
        {
            private static UnityContainer container = new UnityContainer();
    
            private static void Main(string[] args)
            {
                container.RegisterType<Manager>(new ContainerControlledLifetimeManager());
                container.RegisterType<IPerson, Manager>();
                container.RegisterType<IEmployee, Manager>();
                container.RegisterType<IManager, Manager>();
                container.RegisterType<IQueryGenerator<IPerson>, PersonQueryGenerator>();
                container.RegisterType<IQueryGenerator<IEmployee>, EmployeeGenerator>();
                container.RegisterType<IQueryGenerator<IManager>, ManagerQueryGenerator>();
    
                IEnumerable<IManager> managers = new List<IManager>
                {
                    new Manager()
                    {
                        Birthday = DateTime.Now.AddDays(1),
                        Name = "Executive Manager",
                        EmployeeNumber = 9,
                        IsExecutive = true,
                        Salary = 1000
                    },
                    new Manager()
                    {
                        Birthday = DateTime.Now.AddDays(-1),
                        Name = "Ordinary Manager",
                        EmployeeNumber = 8,
                        IsExecutive = false,
                        Salary = 900
                    },
                };
    
                // Fetch queries and return them.
                var queries = new List<Func<IManager, bool>>();
                queries.AddRange(GenerateQueries<IPerson>());
                queries.AddRange(GenerateQueries<IEmployee>());
                queries.AddRange(GenerateQueries<IManager>());
    
                managers = queries.Aggregate(managers, (current, expression) => current.Where(expression));
                foreach (var manager in managers)
                {
                    Console.WriteLine("Manager: {0}", manager.Name);
                }
    
                Console.ReadKey();
            }
    
            // I want to use this method instead.
            private static IEnumerable<Func<TModel, bool>> GenerateQueries<TModel>()
            {
                var queryGenerator = container.Resolve<IQueryGenerator<TModel>>();
    
                return queryGenerator.GenerateQueries();
            }
        }
    
        public interface IQueryGenerator<in T>
        {
            IEnumerable<Func<T, bool>> GenerateQueries();
        }
    
        public class ManagerQueryGenerator : IQueryGenerator<IManager>
        {
            public IEnumerable<Func<IManager, bool>> GenerateQueries()
            {
                Console.WriteLine("ManagerQueryGenerator called");
                yield return x => x.IsExecutive;
            }
        }
    
        public class PersonQueryGenerator : IQueryGenerator<IPerson>
        {
            public IEnumerable<Func<IPerson, bool>> GenerateQueries()
            {
                Console.WriteLine("PersonQueryGenerator called");
                yield return x => x.Birthday > DateTime.Now;
            }
        }
    
        public class EmployeeGenerator : IQueryGenerator<IEmployee>
        {
            public IEnumerable<Func<IEmployee, bool>> GenerateQueries()
            {
                Console.WriteLine("EmployeeGenerator called");
                yield return x => x.Salary > 900;
            }
        }
    
        public interface IHaveName
        {
            string Name { get; set; }
        }
    
        public interface IPerson : IHaveName
        {
            DateTime Birthday { get; set; }
        }
    
        public interface ICustomer : IPerson
        {
            int MoneyToSpend { get; set; }
        }
    
        public interface IEmployee : IPerson
        {
            int EmployeeNumber { get; set; }
            int Salary { get; set; }
        }
    
        public interface IManager : IEmployee
        {
            bool IsExecutive { get; set; }
        }
    
        public class Manager : IManager
        {
            public string Name { get; set; }
            public DateTime Birthday { get; set; }
            public int EmployeeNumber { get; set; }
            public int Salary { get; set; }
            public bool IsExecutive { get; set; }
        }
    }
    

    【讨论】:

    • PersonQueryGenerator 称为 EmployeeGenerator 称为 ManagerQueryGenerator 称为 Manager:执行经理
    猜你喜欢
    • 2016-05-13
    • 1970-01-01
    • 1970-01-01
    • 2015-03-23
    • 1970-01-01
    • 2023-03-27
    • 2011-07-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多