【问题标题】:Why use generic constraints in C#为什么在 C# 中使用泛型约束
【发布时间】:2010-11-01 23:08:08
【问题描述】:

我在 MSDN 上阅读了一篇关于 C# 泛型的优秀文章。

我脑海中突然出现的问题是——我为什么要使用通用约束?

例如,如果我使用这样的代码:

public class MyClass<T> where T : ISomething
{
}

我不能用ISomething切换这个类中T的所有引用吗?

使用这种方法有什么好处?

【问题讨论】:

标签: c# generics constraints


【解决方案1】:

你问,“我不能在这个类中用ISomething 切换所有T 的引用吗?”所以我认为你的意思是比较:

public class MyClass<T> where T : ISomething 
{ 
    public T MyProperty { get; set; }
}

与:

public class MyClass 
{
    public ISomething MyProperty { get; set; }
}

在第二个例子中,MyProperty 只保证是ISomething 的一个实例。在第一个示例中,MyPropertyT 的任何内容,即使它是 ISomething 的特定子类型。考虑ISomething的具体实现:

public class MySomething : ISomething
{
    public string MyOtherProperty { get; set; }
}

现在,如果我们使用第一个通用示例,我们可以:

MyClass<MySomething> myClass = new MyClass<MySomething>();
Console.WriteLine(myClass.MyProperty.MyOtherProperty);

另一方面,如果我们使用第二个示例,我们将无法访问MyOtherProperty,因为它只知道是ISomething

MyClass myClass = new MyClass();
Console.WriteLine(myClass.MyProperty.MyOtherProperty); // Won't compile, no property "MyOtherProperty"

另一方面,这些类型约束有用的原因是您可以引用MyProperty(类型T)并访问ISomething 的成员。换句话说,如果 ISomething 被声明为:

public interface ISomething 
{
    public string SomeProperty { get; set; }
}

然后您可以访问MyProperty.SomeProperty。如果您省略了where T : ISomething,那么您将无法访问SomeProperty,因为T 只会被认为是object 类型。

【讨论】:

    【解决方案2】:

    类型安全。例如,假设您正在创建一个容器。您可以将某些内容传递给该容器并以适当的形式检索它,而无需稍后通过参数化容器进行任何强制转换。您只是定义了您愿意存储在容器中的事物类型的约束。

    【讨论】:

      【解决方案3】:

      这里是一个不同的例子,只使用List&lt;&gt;

      图像列表不会是通用的,但它只会使用IListElement 代替它使用通用的任何地方。现在想象一下,你有一个类似这样的对象。

      class Element : IListElement
      {
         public string Something { get; set; }
      }
      

      现在我可以只做list.Add(element); 并且与真正的List&lt;Element&gt; 没有区别。但是,当我检索数据时,情况就不同了,如果我使用使用 IListElement 的列表,那么我必须将我的数据转换回来,以便从中获取 Something。因此我必须这样做:

      string s = ((Element)list[0]).Something;
      

      而使用泛型我可以做到:

      string s = list[0].Something;
      

      省去了很多麻烦,当然它比这更进一步,但我认为你可以从中得到想法。

      【讨论】:

        【解决方案4】:

        首先,您可以在代码中为泛型方法/泛型类上的方法调用 ISomething 中定义的方法。如果允许 T 为任何类型,那么这是不可能的(尽管您总是可以进行一些运行时强制转换)。

        因此,它允许您对 T 可以是什么实施编译时约束,因此在您编写代码时依赖这些约束 - 将运行时错误转化为编译时错误。

        【讨论】:

          【解决方案5】:

          是的,您可以使用 ISomething 代替 T ,但这会手动关闭普通类的泛型类型。它不再是泛型类型。通过使用 T,您可以将类型 open 保持为您想要的任意数量的 ISomething 子类型。 在不影响类型安全的情况下重用代码是这里的主要优势。例如,如果您使用 ISomething 堆栈,您可以将任何 ISomething 压入堆栈,但必须发生弹出并向下转换为实际子类型的 ISomething 以使其有用。向下转换会创建一个潜在的故障点,它不会出现在通用 Stack&lt;T&gt; 中,其中 T:ISomething

          【讨论】:

            【解决方案6】:

            您的班级的消费者会受益于增加的类型安全性等。

            class Widget : IPokable { }
            
            // No generics
            Widget w = (Widget)list[0]; // cast can fail
            
            // With generics
            Widget w = list[0];
            

            如果没有泛型,如果列表包含 IPokable 对象,仍然需要强制转换

            您正在实现的类受益于在通用对象上使用特定方法。

            class PokableList<T> where T : IPokable {
                public T PokeAndGet() {
                    currentObj.Poke();
                    return currentObj;
                }
            }
            

            【讨论】:

              【解决方案7】:

              这个 youtube 视频实际上展示了通用约束 https://www.youtube.com/watch?v=GlqBRIgMgho 的重要性。

              下面是一个很长的文字答案。

              “泛型有助于将逻辑与数据类型分离。这样我们 附加任何具有任何逻辑的数据类型以实现高可重用性。”

              但很多时候,某些逻辑只能附加到特定的数据类型。

              public class CompareNumeric<UNNKOWDATATYPE>
              {
                      public bool Compareme(UNNKOWDATATYPE v1, UNNKOWDATATYPE v2)
                      {
                          if (v1 > v2)
                          {return true;}
                          else
                         {return false;}
                      }
              }
              

              例如上面是一个简单的泛型类,如果一个数字大于另一个数字,它会进行比较。现在,大于和小于比较非常特定于数字数据类型。这种比较不能在字符串等非数字类型上进行。

              因此,如果有些人使用带有“int”类型的类,它完全有效。

              CompareNumeric<int> obj = new CompareNumeric<int>();
              bool boolgreater = obj.Compare(10,20);
              

              如果有人使用“双”数据类型再次完全有效。

              CompareNumeric<double> obj = new CompareNumeric<double>();
              bool boolgreater = obj.Compare(100.23,20.45);
              

              但是在这种逻辑下使用字符串数据类型会导致不良结果。所以我们想限制或限制什么样的类型可以附加到泛型类上,这是通过使用“泛型约束”来实现的。

              CompareNumeric<string> obj = new CompareNumeric<string>();
              bool boolgreater = obj.Compare(“interview”,”interviewer”);
              

              可以通过在泛型类之后使用“WHERE”关键字指定数据类型来限制泛型类型,如下面的代码所示。现在,如果任何客户端尝试将“字符串”数据类型附加到下面的类中,它将不允许,从而避免不良结果。

              public class CompareNumeric<UNNKOWDATATYPE> where UNNKOWDATATYPE : int, double
              {
              
              }
              

              【讨论】:

              【解决方案8】:

              也许这个简单的例子可能会有所帮助。

              如果我有这些课程:

              public class ListOfCars<T> : List<T> where T : Car { }
              
              public abstract class Car { }
              public class Porsche : Car { }
              public class Bmw : Car { }
              

              ...然后如果我写这段代码:

              var porsches = new ListOfCars<Porsche>();
              
              // OK
              porsches.Add(new Porsche());
              
              //Error - Can't add BMW's to Porsche List
              porsches.Add(new Bmw());
              

              您可以看到我无法将 BMW 添加到 Porsche 列表中,但如果我只是在基类之外进行编程,它将被允许。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2020-09-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多