使用泛型类作为另一个泛型类

时间:2017-06-21 00:26:12

标签: c# generics interface

所以我试图在界面中创建一个属性列表,如下所示:(仅为示例使用1个属性)

public interface IRepository
{
    IEnumerable<Budget> Budgets { get; set; }
}

但我希望能够使用IEnumerable的不同实现来创建此实现。例如,我希望能够实现IRepository并将'Budgets'作为List或DBSet(或者我制作的任何其他自定义类来实现IEnumberable)

我希望我能做到这样的事情:

public interface IRepository<T>
{
    // Database
    T<Budget> Budgets { get; set; }
}

public interface IRepository<T>
where T : IEnumerable<>
{
    // Database
    T<Budget> Budgets { get; set; }
}

然后我可以像这样实现它:

public class Repository : IRepository<List>
{
    List<Budget> Budgets { get; set; }
}

但它的语法无效。

这是否可能,如果没有,我可能有什么替代方案?

4 个答案:

答案 0 :(得分:1)

如果您只想支持Budget,那么您可以使用第二个示例

public interface IRepository<T>
where T : IEnumerable<Budget>
{
    // Database
    T Budgets { get; set; }
}

如果你想支持的不只是Budget作为内部类型,你需要使用两种泛型类型

public interface IRepository<T, U>
where T : IEnumerable<U>
{
    // Database
    T Budgets { get; set; }
}

答案 1 :(得分:0)

  

但我希望能够使用IEnumerable的不同实现创建此实现。

这正是您的代码已经允许的内容。

public interface IRepository
{
    IEnumerable<Budget> Budgets { get; set; }
}

此接口的各种实现可以返回IEnumerable<Budget>的不同实现。

这是一种接近它的方法。如果一个类依赖于一个存储库,那么从该类依赖于它的角度编写该接口。这样,该类不仅仅是采用任何可用的接口。它“拥有”界面。它说它需要什么。

最终的具体实施取决于技术决策。您可能有一个返回DBSet<Budget>的后端。在这种情况下,具体实现将返回。

最后,写一个适配器。它实现了消费者所需的接口。它调用具体的存储库并返回消费者需要的任何内容。

这听起来像是更多的代码(而且它是),但这就是你所完成的。

  • 您的应用程序逻辑与存储库的具体实现分离。
  • 您只需编写所需的代码。您只需在需要时编写适配器,而不是编写具体存储库的大量备用版本。因此,与最初的方法相比,它将减少代码。

答案 2 :(得分:0)

这个例子适合我。您可以为可以从数组中填充的任何IEnumerable提供类型参数。

构造具体的IEnumerable并填充它有点棘手,因为IEnumerable缺少任何添加到集合的方法,但我通过使用带有数组的构造函数来解决它。许多类型都有它。  我的示例使用List<Budget>ConcurrentQueue<Budget>执行两项测试,并且工作正常。

我不知道你怎么可能计划使用这个东西来生成没有构造函数的IEnumerable类型。例如,如果你想要Dictionary<Budget>,那么工厂(只运行通用代码)可能知道如何填充密钥?看起来像某个地方你需要一些非通用的代码。

public class Budget
{
    public string Name { get; set; }
}

public interface IRepository<T> where T : class, IEnumerable<Budget>, new()
{

    T Budgets { get; }
}

public class Repository<T> : IRepository<T> where T : class, IEnumerable<Budget>, new() 
{
    public T Budgets
    {
        get
        {
            Budget[] data = GetData();
            var c = typeof(T).GetConstructor(new [] {typeof(Budget[])});
            object o = c.Invoke(new object[] { data });

            return o as T;
        }
    }

    private Budget[] GetData()
    {
        var a = new Budget[2];
        a[0] = new Budget { Name = "Hello world!" };
        a[1] = new Budget { Name = "Goodbye world!" };

        return a;
    }
}

class Example
{
    static public void TestList()
    {

        Repository<List<Budget>> r = new Repository<List<Budget>>();
        var list = r.Budgets;

        foreach (var b in list)
        {
            Console.WriteLine(String.Format("{0}", b.Name));
        }
    }
    static public void TestQueue()
    {

        Repository<ConcurrentQueue<Budget>> r = new Repository<ConcurrentQueue<Budget>>();
        var list = r.Budgets;

        foreach (var b in list)
        {
            Console.WriteLine(String.Format("{0}", b.Name));
        }
    }
}

答案 3 :(得分:0)

如果目标是能够获得相同的数据,但是在各种数据结构中,我是否可以建议您使用一些控制反转并让调用者注入结构?这样他也可以提供自己的自定义数据结构。

例如:

public void GetBudgets<T>(T collection) where T : ICollection<Budget>
{
    Db.GetBudgetData().ToList().ForEach(a => collection.Add(a));
}

您可以致电:

var r = new Repository();
var list = new System.Collections.Generic.HashSet<Budget>(); //Or any ICollection
r.GetBudgets(list);  //Type is inferred, so no need for <HashSet<Budget>>

或者:

var r = new Repository();
var list = new CustomCollection<Budget>(); 
r.GetBudgets(list);

为方便调用者,您还可以提供不需要注入的通用方法,因此当不需要自定义集合时,调用者可以使用较短的代码:

public T GetBudgets<T>() where T : ICollection<Budget>, new()
{
    T collection = new T();
    GetBudgets(collection);
    return collection;
}

可以用一行调用:

var budgets = repository.GetBudgets<List<Budget>>();

使用这种方法使调用者非常容易。他们将不再需要为他们想要使用的每种不同数据类型实例化新的Repository<T>;他们只需要调用该方法。而且你不再需要弄清楚如何实现IRepository<T>,从而完全避免这个问题。

如果你需要支持ICollection后代以外的数据类型,你需要一个单独的方法,因为数据布局是如此不同(例如DataTable有列,你需要选择哪里去哪里)通用代码是不可能的(Add并非普遍适用于所有IEnumerable类型)。但是您不需要单独的课程,只需使用prototype overload