为什么C#不允许泛型属性?

29

我想知道为什么在非泛型类中不能像编写泛型方法那样编写泛型属性。例如:

public interface TestClass
{
   IEnumerable<T> GetAllBy<T>(); //this works

   IEnumerable<T> All<T> { get; } //this does not work
}

我读了@Jon Skeet的回答,但它只是一个陈述,很可能在规范的某个地方已经有了。

我的问题是为什么会这样?这种限制避免了哪些问题?


9
答案可能是“它没有任何意义”。对我来说,属性是关于状态的,而方法是关于行为的。将行为泛化应用并且类型不重要是有意义的,同样,相同的方法可以通用地适用于实例中许多不同类型的执行以及多个不同类型的执行,然而状态在同等程度上泛化应用是没有意义的。如果您需要通用状态,则需要一个通用类。但这只是我的一些想法。 - Anthony Pegram
7
@AnthonyPegram在这里说得很对。一件事情的属性如何成为参数化多态?事物的属性是其颜色、高度、重量等,属性的整个意义在于它们不是参数化的。将属性与形式参数参数化是没有意义的,而且说“我想让我的汽车类具有一个Weight<Fruit>属性,该属性与Weight<Giraffe>不同”也毫无意义。用类型参数化一个属性又是什么意思呢? - Eric Lippert
2
@EricLippert 泛型属性似乎很方便,例如:class Figure { public ColorT Color<ColorT> { get { ... } } ... }(可能对ColorT有一些约束)以返回所需颜色空间中的颜色。或者使用不同单位的量来参考您的示例。 - BartoszKP
3
此外,“GetWeight<Giraffe>()”也没有意义,但我猜想你并不认为泛型方法是无意义的。 - BartoszKP
@BartoszKP,这不是关于那个调用是否有意义的问题;Eric评论中的关键是“属性的整个重点在于它们没有参数”,而<Giraffe>显然是一个参数 - 具体来说是泛型类型参数。 - Roman Starkov
@RomanStarkov,这是关于这个问题的,因为Eric关于为什么属性不是参数化的主要论点是Weight<Giraffe>没有意义。我指出,这同样适用于通用方法,因此这不能成为没有通用属性的明智理由。 - BartoszKP
5个回答

16

从技术上讲,CLR仅支持泛型类型和方法,而不支持属性,因此问题是为什么它没有添加到CLR中。答案很可能是“它被认为不能带来足够的好处来抵消成本”。

但更根本的是,它被认为没有带来任何好处,因为在语义上没有意义将属性参数化为类型。一个Car类可以有一个Weight属性,但是拥有一个Weight<Fruit>和一个Weight<Giraffe>属性是没有意义的。


2
我认为如果你看一下其他答案,就会明白为什么他们不允许这样做,这不仅仅是因为时间太长的问题。 - viggity
@viggity:我的答案不只是“太多时间”的问题。此外,我还在其他回答中留下了评论。简而言之,它们并没有回答这个问题。 - Timwi
2
我不理解“语义上没有意义的参数”的含义。就像在类上的通用方法一样,目的通常是使类的子类型成为通用类型,而不是将整个类变成它不是的东西(例如将汽车变成水果)。更有可能的是使类的内部组件(如内部和外部电子设备或燃料、刹车、转速表)的某些操作(在属性的情况下是获取或设置)成为通用的,其中一个操作同样适用于每个操作。听起来更可能是因为太难了而被搁置了。 - acarlon
尽管这是一个边界情况,如果允许通用属性,则可能会导致语义无意义,例如具有通用手部属性的大猩猩类,从而使大猩猩对象拥有黑猩猩的手。因此,也许不仅是我最初想象的太难了。 - acarlon
1
并不是说通用属性没有意义,因为Weight<Fruit>没有意义。对于每个功能,您都可以构造一个愚蠢的示例,这并不意味着该功能根本没有意义。在这种情况下,请考虑Weight<Kilogram>,它确实有意义(或者请参见我在问题下的另一个示例)。 - BartoszKP
显示剩余4条评论

13

这篇来自Julian Bucknall的通用属性博客文章是一个相当不错的解释。本质上,它是一个堆分配问题。


5
和另一个回答一样,这篇博客文章完全没有涉及到通用属性的问题,只涉及到通用字段。这并没有解释为什么我们不能有通用属性。 - Timwi
4
正是我想要表达的。问题都出在“字段”上。博客文章中没有解释为什么我们不能有没有“字段”的“通用属性”。属性只是一个get方法(可选的set方法),完全可以是通用的。只有“字段”不能这样做。 - Timwi
4
那篇文章相当糟糕。它假设所有属性都有一些后备存储空间,但实际情况并非如此。 - munificent
3
想象一个异种集合类。如果有一个获取给定类型集合中所有对象的操作,那会很不错。如果您有泛型属性,它可以是collection.OfType<Blah>。但实际上,必须使用方法collection.OfType<Blah>(),即使它不需要参数。 - munificent
1
这里有一件事情很奇怪:T Get<T>和void Set<T>(T t)是可以的。因此,T Foo<T> { get { ... } set { ...} }也是可能的。如果有某种愚蠢的内部原因导致这种情况不成立,编译器可以在此之前进行转换,因为除了自动属性和反射(可能会受到限制)之外,这两者在功能上是相同的,只是一个语法更加优雅。 - Hatchling
显示剩余5条评论

3

我的猜测是它可能存在一些让语法模糊的棘手问题。一时之间,这似乎有点棘手:

foo.Bar<Baz>=3;

这应该解析为:

foo.Bar<Baz> = 3;

或者:

foo.Bar < Baz >= 3;

9
实际上,这个问题已经存在于泛型方法中(M(a<b,c>(d+1))M(a < b, c > (d+1))的区别,甚至比你举的例子更不刻意),并且已经明确决定采取破坏性变更。 - Timwi
@Timwi 这真是出乎意料。我本以为a、b、c、d是局部变量,M是一个接受2个布尔值的方法。这样做最合理,因为如果您定义了与字段、属性、非泛型方法甚至类型(包括泛型和非泛型)或命名空间相同名称的局部变量,则由于遮蔽,局部变量优先。很奇怪的是,遮蔽几乎可以适用于任何除泛型方法以外的东西。我对此限制并不介意,但我确实觉得它很奇怪。 - AnorZaken

0

我做了类似的东西。 它可以在运行时进行类型检查。

public class DataPackage
{
    private dynamic _list;

    public List<T> GetList<T>()
    {
        return (List<T>)_list;
    }

    public void SetList<T>(List<T> list)
    {
        _list = list;
    }

    public string Name { get; set; }
}

0

我认为不使用自动getter/setter说明为什么在类级别没有定义"T"是不可能的。

尝试编码,自然而然的做法是这样的:

IEnumerable<T> _all;
IEnumerable<T> All
{
    get { return _all; }
}

因为您的领域使用了"T",所以" T "需要在类上,CLR 才知道"T"是什么。

当您使用方法时,可以延迟定义"T",直到实际调用该方法。但是对于字段/属性,"T"需要在一个地方声明,在类级别。

一旦在类上声明了T,创建属性就变得非常容易。

public class TestClass<T>
{
    IEnumerable<T> All { get; }
}

用法:

var myTestClass = new TestClass<string>();
var stuff = myTestClass.All;

就像方法中的“T”类型参数一样,您可以等到实例化TestClass时再定义“T”的具体含义。


3
您的整个回答都是关于字段而不是属性;您仅使用“自动实现属性”的概念来暗示隐式支持字段。这个答案没有回答关于通用属性的问题,而且问题并不是关于“自动实现”的通用属性。 - Timwi
一个更好的例子是IEnumerable<T> _all和IEnumerable<S> All<S>,它将迭代_all T,产生任何类型为S的T。(请注意,这可以通过_all.OfType<S>扩展方法实现。) - yoyo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接