繁体   English   中英

通用类型约束:如何创建 C# 文档中描述的 class 的实例

[英]Generic type constraint: How do I create an instance of the class described in this C# documentation

where(泛型类型约束)(C# 参考)

上面引用的文件中的相关文本是:

例如,您可以声明一个泛型 class,MyGenericClass,这样类型参数 T 就实现了 IComparable<T> 接口:

这是MyGenericClass ,从上面引用的文档中逐字复制:

public class AGenericClass<T> where T : IComparable<T> { }

这是一个小型控制台应用程序,显示了我创建实例的尝试:

class Program
{
    static void Main(string[] args)
    {
        AGenericClass<MyComparable<string>> stringComparer = new AGenericClass<MyComparable<string>>(); // does not build


        AGenericClass<MyStringComparable> stringComparer2 = new AGenericClass<MyStringComparable>(); // does not build
    }
}


public class MyStringComparable : IComparable<string>
{
    public int CompareTo(string other) => throw new NotImplementedException();
}

public class MyComparable<T> : IComparable<T>
{
    public int CompareTo(T other) => throw new NotImplementedException();
}

// verbatim from documentation cited above
public class AGenericClass<T> where T : IComparable<T> { }  

为了清楚起见,我的问题是“如何创建AGenericClass的实例,因为它在引用的 C# 文档中定义”。 请注意,我的问题与上面引用的具体示例有关,而不是其他相关问题,例如 this one 和 this one

我显然对类型参数的工作方式感到非常困惑。 我希望通过回答这个问题,我会有所启发,因为它与我试图解决的业务问题非常相似。

此外,我的问题与IComparable<T>或比较对象无关 - 这恰好是示例代码中的接口。

编辑:根据@CoolBots 的回复提供的附加代码。 此代码提供了一个更真实的示例,并显示了一个旨在在单个 object 上运行的接口:

public class Program2
{

    public Program2()
    {
        Selector<Selectable<string>, string> stringSelector = new Selector<Selectable<string>, string>();
    }
}

public interface ISelectable<T>
{
    bool IsSelected { get; set; }
    T Item { get; set; }
}

public class Selectable<T> : ISelectable<T>
{
    public bool IsSelected { get; set; }
    public T Item { get; set; }
}

public class Selector<T, TData> where T:ISelectable<TData>
{
}

在您的具体示例中, AGenericClass<T>中的泛型参数T和相应的约束, where T: IComparable<T>指定泛型参数T必须实现IComparable<T>接口(这对您的问题并不重要,但是理解发生了什么很重要,因为这个接口本身是通用的)。 请注意, IComparable<T> TTAGenericClass<T>中的 T 相同 - 也就是说,您限制为实现自身IComparable<T>的类型 - string就是一个很好的例子,因为它是IComparable<string>但是,您的MyStringComparable不是IComparable<MyStringComparable> - 它是一个IComparable<string> ,它不符合定义的AGenericClass<T>的泛型类型约束。

这里的解决方案是使用 2 个泛型参数 - 一个用于容器,一个用于包含的类型:

class AGenericClass<TContainer, TData> where TContainer: IComparable<TData> { }

实例化如下:

var stringComparer = new AGenericClass<MyComparable<string>, string>();

var stringComparer2 = new AGenericClass<MyStringComparable, string>();

编辑根据您更新的问题和评论:

如果您真的想摆脱第二个通用参数,则必须提供一些东西- 例如,我们可能会失去指定容器的能力,使其成为pass-through ,类似于System.Collections.Generic.LinkedList<T> ' s 实现 - LinkedListNode<T>是一个直通容器,它是LinkedList<T> class 已知的,不能换成不同类型的容器:

public class Selector<T> //no constraint at all
{
   private Selectable<T> container; //the container is always Selectable<T>

   public Selector(T item)
      => container = new Selectable<T>() { Item = item };
}

用法更简单:

var selector = new Selector<string>("some data"); // no need to pass Selectable<string> here

但是,您现在坚持将Selectable<T>作为容器 - 您不能制作BetterSelectable<T>并将其用作容器可互换。 基本上,这取决于您的用例 - 您是否需要 2 个变量- 一个“容器”和一个“包含的数据”,或者您只需要一个变量 - 只是包含的数据。 在前一种情况下,您必须有 2 个泛型参数(因为您有 2 个变量); 在后一种情况下,您只需要 1 个通用参数(因为您有 1 个变量和一个常量,即容器)。

为了清楚起见,我的问题是“如何创建 AGenericClass 的实例,因为它在引用的 C# 文档中定义”

通过为类型参数T提供满足约束T: IComparable<T> 在您的代码示例中:

static void Main(string[] args)
{
    AGenericClass<MyComparable<string>> stringComparer = new AGenericClass<MyComparable<string>>(); // does not build


    AGenericClass<MyStringComparable> stringComparer2 = new AGenericClass<MyStringComparable>(); // does not build
}

您会收到编译时错误,因为您提供的任何类型都不满足该约束。 修复错误的方法是提供一个满足约束的类型。

有很多类型已经满足了约束。 例如, int或任何其他原始数字类型都可以。 但大概你的问题真的应该用更像这样的措辞:

为了清楚起见,我的问题是“如何使用我自己的用户定义类型作为类型参数来创建引用的 C# 文档中定义的 AGenericClass 的实例”

答案还是一样的:满足用户定义类型的约束。 那么,为什么你已经拥有的类型不这样做呢? 让我们来看看...

以下是您的类型:

public class MyStringComparable : IComparable<string>
{
    public int CompareTo(string other) => throw new NotImplementedException();
}

public class MyComparable<T> : IComparable<T>
{
    public int CompareTo(T other) => throw new NotImplementedException();
}

让我们一次一个地测试它们。 假设我们将T设置为MyStringComparable 为了满足约束,该类型需要实现IComparable<T>其中T是您作为类型参数提供的类型。 由于您将T设置为MyStringComparable ,因此MyStringComparable需要实现IComparable<MyStringComparable>

可以? 不,不是的。 它为其他一些类型参数T实现IComparable<T> ,即string

为了满足约束,您需要这样的东西:

public class MyStringComparable : IComparable<MyStringComparable>
{
    public int CompareTo(MyStringComparable other) => throw new NotImplementedException();
}

好吧,第二个呢。 使用MyComparable<T>作为类型参数进行相同的测试。 当然,您需要为该 class 提供类型参数,因为它是通用的。 在您的示例中,您使用string作为类型参数,因此它是MyComparable<string>

根据约束,类型参数MyComparable<string>需要实现IComparable<MyComparable<string>> 可以? 不,不是的。 MyComparable<string> class 仅实现IComparable<string> 同样,这是错误的约束。

实现正确约束的 class 看起来更像这样:

public class MyComparable<T> : IComparable<MyComparable<T>>
{
    public int CompareTo(MyComparable<T> other) => throw new NotImplementedException();
}

泛型类型约束可能很难处理。 人们经常纠结的另一个领域是尝试继承或以其他方式使用具有约束的现有类型参数,忘记了他们需要在该上下文中满足约束。 通常这是通过在他们自己的通用 class 或方法中重复约束来完成的。

关于您的问题中的这段文字,我还会注意:

此外,我的问题与 IComparable 或比较对象无关 - 这恰好是示例代码中的接口。

实际上,您的问题至少与IComparable<T>有一定程度的关系,因为它经常以这种递归方式使用,使人们绊倒。 在更传统的情况下,您不会遇到同样的问题。

您遇到的场景是 C++ 的“Curiously Recurring Template Pattern”的 C# 变体。 Eric Lippert 就该主题撰写了一篇有用的文章,您可能希望在探索和了解泛型类型参数及其约束时阅读该文章。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM