Ama*_*Mas 19 c# generics constraints new-operator
我想知道为什么泛型类型参数的新约束只能在没有参数的情况下应用,也就是说,可以将类型约束为具有无参数构造函数,但是不能将类约束为具有构造函数,接收一个int作为参数.我知道这方面的方法,使用反射或工厂模式,工作正常,好吧.但我真的很想知道为什么,因为我一直在思考它,我真的想不出无参数构造函数和带有参数的区别,这些参数可以证明对新约束的这种限制.我错过了什么?非常感谢
@Eric:让我和你在一起一会儿:
构造函数是方法
那么我想如果我这样做,没有人会反对:
public interface IReallyWonderful
{
new(int a);
string WonderMethod(int a);
}
Run Code Online (Sandbox Code Playgroud)
但是一旦我有了,那我就去:
public class MyClass<T>
where T : IReallyWonderful
{
public string MyMethod(int a, int b)
{
T myT = new T(a);
return myT.WonderMethod(b);
}
}
Run Code Online (Sandbox Code Playgroud)
这首先是我想要做的.所以,抱歉,但不,构造函数不是方法,或者至少不完全.
关于实现这个功能的困难,我真的不知道,即使我这样做了,我对于明智地支出股东资金的决定也没什么可说的.这样的话,我会马上把它标记为答案.
从学术(我)的角度来看,也就是说,没有任何关于实施成本的问题,问题确实是(我在最近几个小时内完成了这个问题):
构造函数应该被视为类的实现的一部分,还是作为语义契约的一部分(以同样的方式将接口视为语义契约).
如果我们将构造函数视为实现的一部分,那么,约束泛型类型参数的构造函数不是一件非常通用的事情,因为这会将你的泛型类型绑定到具体的实现,并且几乎可以说为什么完全使用泛型?
作为实现的一部分的构造函数的示例(在指定任何下面的构造函数作为定义的语义契约的一部分时没有意义ITransformer):
public interface ITransformer
{
//Operates with a and returns the result;
int Transform(int a);
}
public class PlusOneTransformer : ITransformer
{
public int Transform(int a)
{
return a + 1;
}
}
public class MultiplyTransformer : ITransformer
{
private int multiplier;
public MultiplyTransformer(int multiplier)
{
this.multiplier = multiplier;
}
public int Transform(int a)
{
return a * multiplier;
}
}
public class CompoundTransformer : ITransformer
{
private ITransformer firstTransformer;
private ITransformer secondTransformer;
public CompoundTransformer(ITransformer first, ITransformer second)
{
this.firstTransformer = first;
this.secondTransformer = second;
}
public int Transform(int a)
{
return secondTransformer.Transform(firstTransformer.Transform(a));
}
}
Run Code Online (Sandbox Code Playgroud)
问题是构造函数也可能被视为语义契约的一部分,如下所示:
public interface ICollection<T> : IEnumerable<T>
{
new(IEnumerable<T> tees);
void Add(T tee);
...
}
Run Code Online (Sandbox Code Playgroud)
这意味着,从一系列元素构建集合总是可行的,对吧?这将成为语义合同的一个非常有效的部分,对吗?
在不考虑股东资金明智支出的任何方面的情况下,我倾向于允许构造函数作为语义契约的一部分.有些开发人员搞砸了它并将某种类型限制为具有语义错误的构造函数,那么,同一个开发人员添加语义错误操作的区别是什么?毕竟,语义契约就是这样,因为我们都同意它们,因为我们都很好地记录了我们的库;)
@supercat一直试图设置一些例子(引用评论)
也很难确切地定义构造函数约束应该如何工作,而不会导致令人惊讶的行为.
但我真的必须不同意.在C#(好吧,在.NET中)的惊喜,如"如何让企鹅飞?" 根本不要发生.关于编译器如何解析方法调用有很简单的规则,如果编译器无法解析它,那么它将无法通过,也不会编译.
他的最后一个例子是:
如果它们是逆变的,那么如果泛型类型具有约束new(Cat,ToyotaTercel),并且实际类型只具有新的构造函数(Animal,ToyotaTercel)和新的(Cat,Automobile),则会遇到麻烦,从而解决应该调用哪个构造函数.
好吧,让我们尝试一下(在我看来,这与@supercat提出的情况类似)
class Program
{
static void Main(string[] args)
{
Cat cat = new Cat();
ToyotaTercel toyota = new ToyotaTercel();
FunnyMethod(cat, toyota);
}
public static void FunnyMethod(Animal animal, ToyotaTercel toyota)
{
Console.WriteLine("Takes an Animal and a ToyotaTercel");
}
public static void FunnyMethod(Cat cat, Automobile car)
{
Console.WriteLine("Takes a Cat and an Automobile");
}
}
public class Automobile
{ }
public class ToyotaTercel : Automobile
{ }
public class Animal
{ }
public class Cat : Animal
{ }
Run Code Online (Sandbox Code Playgroud)
而且,哇,它不会编译错误
以下方法或属性之间的调用不明确:'TestApp.Program.FunnyMethod(TestApp.Animal,TestApp.ToyotaTercel)'和'TestApp.Program.FunnyMethod(TestApp.Cat,TestApp.Automobile)'
我不明白为什么结果应该是不同的,如果同样的问题是由具有参数化构造函数约束的解决方案引起的,如下所示:
class Program
{
static void Main(string[] args)
{
GenericClass<FunnyClass> gc = new GenericClass<FunnyClass>();
}
}
public class Automobile
{ }
public class ToyotaTercel : Automobile
{ }
public class Animal
{ }
public class Cat : Animal
{ }
public class FunnyClass
{
public FunnyClass(Animal animal, ToyotaTercel toyota)
{
}
public FunnyClass(Cat cat, Automobile car)
{
}
}
public class GenericClass<T>
where T: new(Cat, ToyotaTercel)
{ }
Run Code Online (Sandbox Code Playgroud)
当然,现在,编译器无法处理构造函数的约束,但如果可以的话,为什么错误不会在GenericClass<FunnyClass> gc = new GenericClass<FunnyClass>();类似于尝试编译第一个示例时获得的那个错误FunnyMethod.
无论如何,我更进一步.当一个覆盖抽象方法或实现在接口上定义的方法时,需要使用完全相同的参数类型,不允许继承者或祖先.因此,当需要参数化构造函数时,应该使用精确定义来满足需求,而不是其他任何需求.在这种情况下,对于类FunnyClass的泛型参数类型,永远不能将类指定为类型GenericClass.
Eri*_*ert 14
Kirk Woll的引用当然是我需要的所有理由; 我们不需要为不存在的功能提供理由.功能有巨大的成本.
但是,在这个特定的情况下,我当然可以给你一些理由,如果它在设计会议中作为未来语言版本的一个可能功能出现,我将推迟使用该功能.
首先:考虑更一般的功能.构造函数是方法.如果你希望有一种方法可以说"类型参数必须有一个带有int的构造函数"那么为什么说"类型参数必须有一个名为Q的公共方法,它需要两个整数并返回一个串?"
string M<T>(T t) where T has string Q(int, int)
{
return t.Q(123, 456);
}
Run Code Online (Sandbox Code Playgroud)
这对你来说是一个非常通用的事情吗?这似乎与泛型具有这种约束的想法背道而驰.
如果该特性对于方法来说是一个坏主意,那么为什么对于恰好是构造函数的方法来说这是一个好主意呢?
相反,如果方法和构造者是一个好主意,那么为什么要停在那里呢?
string M<T>(T t) where T has a field named x of type string
{
return t.x;
}
Run Code Online (Sandbox Code Playgroud)
我说我们要么应该做整个功能,要么根本不做.如果能够将类型限制为具有特定构造函数是很重要的,那么让我们执行整个功能并基于成员而不仅仅是构造函数来限制类型.
当然,该功能的设计,实施,测试,文档和维护成本更高.
第二点:假设我们决定实现该功能,"只是构造函数"版本或"任何成员"版本.我们生成什么代码? 关于通用codegen的事情是它经过精心设计,以便您可以进行一次静态分析并完成它.但是没有标准的方法来描述IL中的"调用带有int的构造函数".我们必须向IL添加新概念,或者生成代码,以便通用构造函数调用使用Reflection.
前者很贵; 改变IL中的基本概念是非常昂贵的.后者是(1)慢,(2)框参数,(3)是您自己编写的代码.如果您要使用反射来查找构造函数并调用它,那么编写使用反射的代码来查找构造函数并调用它. 如果这是代码生成策略,那么约束赋予的唯一好处是传递一个没有带有int的公共ctor的类型参数的错误在编译时而不是运行时被捕获.你没有得到泛型的任何其他好处,比如避免反射和拳击惩罚.
概括
这是尝试捕获有关此问题的当前信息和解决方法并将其作为答案呈现。
我发现泛型与约束的结合是 C# 最强大、最优雅的方面之一(来自 C++ 模板背景)。where T : Foo很棒,因为它引入了 T 的功能,同时在编译时仍将其限制为 Foo。在许多情况下,它使我的实现更加简单。起初,我有点担心以这种方式使用泛型类型会导致泛型在代码中增长,但我允许它这样做,而且好处远远超过了任何缺点。然而,在构造带有参数的泛型类型时,抽象总是会失败。
问题
当约束泛型类时,您可以指示泛型必须具有无参数构造函数,然后实例化它:
public class Foo<T>
where T : new()
{
public void SomeOperation()
{
T something = new T();
...
}
}
Run Code Online (Sandbox Code Playgroud)
问题是只能对无参数构造函数进行约束。这意味着下面建议的解决方法之一需要用于具有参数的构造函数。如下所述,这些解决方法存在一些缺点,从需要额外的代码到非常危险。另外,如果我有一个类,它有一个由泛型方法使用的公共无参数构造函数,但在某个地方该类被更改,以便构造函数现在有一个参数,那么我需要更改模板和周围的设计代码使用其中一种解决方法而不是 new()。
Microsoft 肯定知道这一点,请参阅 Microsoft Connect 上的这些链接作为示例(不包括困惑的 Stack Overflow 用户提出的问题)这里 这里 这里 这里 这里 这里 这里。
它们都被关闭为“无法修复”或“按设计”。可悲的是,该问题随后被锁定,无法再对它们进行投票。不过,您可以在此处为构造函数功能投票。
解决方法
解决方法主要有三种类型,但没有一种是理想的:-
解释
Microsoft Connect 上提供了标准响应:
“感谢您的建议。微软已经收到了许多关于更改泛型类型约束语义的建议,并在该领域开展了自己的工作。但是,目前微软无法保证该领域的更改将成为未来产品版本的一部分。我们将记录您的建议,以帮助推动该领域的决策。同时,下面的代码示例...”
该解决方法实际上不是我推荐的所有选项的解决方法,因为它不是类型安全的,如果您碰巧向构造函数添加另一个参数,则会导致运行时异常。
我能找到的最好的解释是 Eric Lippert 在这篇堆栈溢出帖子中提供的。我非常赞赏这个答案,但我认为需要在用户级别以及技术级别上对此进行进一步讨论(可能是比我更了解内部结构的人)。
我最近还发现此链接中有 Mads Torgersen 的精彩详细内容(请参阅“Microsoft 发布于 2009 年 3 月 31 日下午 3:29”)。
问题在于构造函数与其他方法不同,因为我们已经可以通过派生约束(接口或基类)来尽可能多地约束方法。在某些情况下,方法约束可能是有益的,我从未需要它们,但我不断遇到无参数构造函数的限制。当然,通用(不仅仅是构造函数)解决方案将是理想的,微软需要自己决定这一点。
建议
关于有争议的好处与实施难度,我可以理解这一点,但我会提出以下几点:-
现状
据我所知,这不会以任何形式发生。
| 归档时间: |
|
| 查看次数: |
5641 次 |
| 最近记录: |