【问题标题】:Using a Generic Class as a type parameter in another generic class在另一个泛型类中使用泛型类作为类型参数
【发布时间】:2017-06-21 00:26:12
【问题描述】:

我正在尝试在这样的界面中创建属性列表:(仅使用 1 个属性作为示例)

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

但我希望能够使用IEnumerable&lt;T&gt; 的不同实现来创建它的实现。因此,例如,我希望能够实现 IRepository 并将“预算”作为列表或 DbSet(或我创建的任何其他实现 IEnumerable&lt;T&gt; 的自定义类)

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

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; }
}

但它的语法无效。

这有可能吗?如果没有,我还有什么选择?

【问题讨论】:

  • 只是好奇:你为什么要这个?
  • 嘿 Luke 看看“存储库”模式,这正是你想要的!
  • 您可以在通用接口IRepository&lt;T&gt; where T : IEnumerable&lt;Budget&gt; 上指定T Budgets { get; }。但是为了什么目的.. 接口可能是非通用的暴露 IQueryableIEnumerable
  • @Bas 我想这样做的主要原因是因为我想实现多个类似“存储库”的类,它们都具有相同类型的属性(例如预算)但具有不同的 IEnumerable 实现。如果我把它做成简单的 IEnumerable 那么我所有的实现都是 IEnumerable 并且我必须进行强制转换才能获得我不想做的 List 或 DBSet (或其他)。

标签: c# generics interface


【解决方案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; }
}

【讨论】:

  • 这适用于给定的示例,但在添加更多属性时会变得混乱,并且对于大量属性完全不起作用
  • 感谢您的回答,但是在第二个示例中是否有不需要提供“U”的解决方案?因为,在我的模型中,属性可能会增加到十几个甚至更多,我希望它们是固定的而不是通用的,它只是我想要更改的 IEnumerable 类型(在您的示例中为 T)跨度>
  • @LukeBourne 这开始闻起来像XY problem,我不太确定您尝试做的事情是否真的有用。
【解决方案2】:

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

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

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

这个接口的各种实现可以返回IEnumerable&lt;Budget&gt;的不同实现。

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

最终的具体实施取决于技术决策。您可能有一个返回DBSet&lt;Budget&gt; 的后端。在这种情况下,这就是具体实现将返回的内容。

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

这听起来像是更多的代码(确实如此),但这就是你要完成的事情。

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

【讨论】:

  • 谢谢,这是我试图避免的解决方案,但可能是我正在尝试做一些不可能、过于复杂或两者兼而有之的事情。所以澄清一下,如果我想要一个将每个属性表示为列表的class ListRepository 和一个将每个属性表示为 DBSet 的class DBSetRepository,你是说我应该创建 2 个单独的接口吗?还是保留我的 1 接口并创建一个返回我想要的实现的适配器?
【解决方案3】:

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

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

我不知道你怎么可能打算使用这个东西来生成没有带数组的构造函数的IEnumerable 类型。例如,如果您想要Dictionary&lt;Budget&gt;,工厂(只运行通用代码)怎么可能知道如何填充键?似乎在某个地方你需要一些非通用代码。

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

【讨论】:

  • 感谢您的回答,如果我在界面中有 10-20 个不同的属性,这将如何工作?
  • 你添加它们?我没有看到问题。哪个界面?
  • 当你实例化你的 Repository&lt;List&lt;Budget&gt;&gt; 时,你必须指定属性本身,所以如果我在 IRepository 中有 2 个其他属性:WalletCash 使用你的唯一方法方法类似于:Repository&lt;List&lt;Budget&gt;,List&lt;Wallet&gt;,List&lt;Cash&gt;&gt;。如果我有 20 个属性,那么这是不可行的。
  • 我明白了。也许使用通用方法而不是通用接口,例如而不是Budgets 为什么不是GetBudgets&lt;List&lt;Budget&gt;&gt;()
【解决方案4】:

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

例如:

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&lt;T&gt;;他们只需要调用该方法。并且您不再需要弄清楚如何实现IRepository&lt;T&gt;,从而完全避免了这个问题。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-10-31
    • 1970-01-01
    • 2014-08-02
    • 1970-01-01
    • 2021-12-25
    • 2019-06-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多