c# - 为什么这个通用约束在似乎有循环引用时编译

标签 c# generics constraints

我在 csharp 中为 MVCContrib Html 帮助程序编写了一个扩展方法,并对通用约束的形式感到惊讶,从表面上看,它似乎通过类型参数循环引用自身。

话虽如此,该方法可以按预期编译和工作。

我很乐意有人解释为什么这样做,如果存在更直观的直觉语法,如果不存在,是否有人知道为什么?

这是编译和功能代码,但我删除了 T 列表示例,因为它掩盖了问题。 以及使用 List 的类似方法。

namespace MvcContrib.FluentHtml 
{
  public static class FluentHtmlElementExtensions
  {
    public static TextInput<T> ReadOnly<T>(this TextInput<T> element, bool value)
        where T: TextInput<T>
    {
        if (value)
            element.Attr("readonly", "readonly");
        else
            ((IElement)element).RemoveAttr("readonly");
        return element;
    }
  }
}

<罢工>

<罢工>
    /*analogous method for comparison*/
    public static List<T> AddNullItem<T>(this List<T> list, bool value) 
        where T : List<T>
    {
        list.Add(null);
        return list;
    }

<罢工>

在第一种方法中,约束 T : TextInput 似乎是循环的。但是,如果我将其注释掉,则会出现编译器错误:

“类型‘T’不能用作泛型类型或方法‘MvcContrib.FluentHtml.Elements.TextInput’中的类型参数‘T’。 没有从“T”到“MvcContrib.FluentHtml.Elements.TextInput”的装箱转换或类型参数转换。”

<罢工> 在 List 的情况下,错误是:

“'System.Collections.Generic.List.Add(T)' 的最佳重载方法匹配有一些无效参数 参数 1:无法从“”转换为“T””

我可以想象一个更直观的定义是包含 2 种类型,一个是对通用类型的引用,一个是对约束类型的引用,例如:

public static TextInput<T> ReadOnly<T,U>(this TextInput<T> element, bool value) 
    where U: TextInput<T>

public static U ReadOnly<T,U>(this U element, bool value) 
    where U: TextInput<T>

但是这些都不编译。

最佳答案

更新:这个问题是我 blog article on the 3rd of February 2011 的基础.感谢您提出很好的问题!


这是合法的,不是循环的,而且相当普遍。我个人不喜欢它。

我不喜欢它的原因是:

  1. 太聪明了;正如您所发现的,对于不熟悉类型系统复杂性的人来说,聪明的代码很难凭直觉理解。

  2. 它与我对泛型“代表”的直觉不符。我喜欢类来表示事物的类别,而通用类来表示参数化的类别。我很清楚“字符串列表”和“数字列表”都是两种列表,只是列表中事物的类型不同。我不太清楚什么是“T 的 TextInput,其中 T 是 T 的 TextInput”。不要让我思考。

  3. 此模式经常用于尝试在类型系统中强制执行实际上在 C# 中无法强制执行的约束。即这个:

    abstract class Animal<T> where T : Animal<T>
    {
        public abstract void MakeFriends(IEnumerable<T> newFriends);
    }
    class Cat : Animal<Cat>
    {
        public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
    }
    

这里的想法是“Animal 的子类 Cat 只能与其他 Cats 交 friend 。”

问题是所需的规则实际上并没有得到执行:

class Tiger: Animal<Cat>
{
    public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}

现在老虎可以和猫交 friend ,但不能和老虎交 friend 。

要在 C# 中实际执行此操作,您需要执行以下操作:

abstract class Animal 
{
    public abstract void MakeFriends(IEnumerable<THISTYPE> newFriends);
}

其中“THISTYPE”是一个神奇的新语言特性,意思是“需要一个重写类在这里填写它自己的类型”。

class Cat : Animal 
{
    public override void MakeFriends(IEnumerable<Cat> newFriends) {}
}

class Tiger: Animal
{
    // illegal!
    public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}

不幸的是,这也不是类型安全的:

Animal animal = new Cat();
animal.MakeFriends(new Animal[] {new Tiger()});

如果规则是“动物可以与任何同类交 friend ”,那么动物可以与动物交 friend 。但是猫只能和猫交 friend ,不能和老虎交 friend !参数位置中的内容必须反变有效;在这个假设的情况下,我们需要协方差,这是行不通的。

我好像有点跑题了。回到这个奇怪的重复模式的主题:我尝试只将这种模式用于常见的、容易理解的情况,比如其他答案提到的情况:

class SortedList<T> where T : IComparable<T>

也就是说,如果我们希望对它们进行排序,我们需要每个 T 都可以与其他每个 T 进行比较。

要真正被标记为循环,依赖项中必须有一个真正的循环:

class C<T, U> where T : U where U : T

类型理论的一个有趣领域(目前 C# 编译器处理不佳)是非循环但无限 泛型类型的领域。我已经编写了一个 infinitary 类型检测器,但它没有进入 C# 4 编译器,并且对于 future 可能的编译器版本来说,它不是一个高优先级。如果您对无限类型的某些示例或 C# 循环检测器出现问题的某些示例感兴趣,请参阅我的相关文章:

https://ericlippert.com/2008/05/07/covariance-and-contravariance-part-11-to-infinity-but-not-beyond/

关于c# - 为什么这个通用约束在似乎有循环引用时编译,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3783321/

相关文章:

c# - ASP.NET 客户端验证与服务器端验证

iOS - 将两个标签放在 super View 的中心

swift - 根据UIButton高度设置titleLabel

c# - 我可以将 .aspx 页面中的 java 脚本接收到的值传递到 asp.net 中相应的 .cs 文件吗?

c# - 将 JSON 对象包装在另一个对象中的正确方法是什么?

swift - 如何在 Swift 的泛型扩展中使用带有可选类方法的协议(protocol)?

java - <T> 无法解析为类型?

java - 如何修复此使用泛型的代码中的 "method not applicable to arguments"问题

ios - 带约束的 Swift 动画

c# - 并行双向选择排序