限制泛型类型参数具有特定的构造函数 [英] Restricting a generic type parameters to have a specific constructor

查看:36
本文介绍了限制泛型类型参数具有特定的构造函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道为什么对泛型类型参数的新约束只能在没有参数的情况下应用,也就是说,可以约束类型具有无参数构造函数,但不能约束类具有,例如,接收 int 作为参数的构造函数.我知道解决这个问题的方法,使用反射或工厂模式,效果很好,好吧.但我真的很想知道为什么,因为我一直在思考这个问题,我真的想不出无参数构造函数和带有参数的构造函数之间的区别,可以证明对新约束的这种限制是合理的.我错过了什么?非常感谢

I'd like to know why the new constraint on a generic type parameter can only be applied without parameters, that is, one may constraint the type to have the parameterless constructor, but one cannot constraint the class to have, say, a constructor that receives a int as a parameter. I know ways around this, using reflection or the factory pattern, that works fine, ok. But I'd really like to know why, because I've been thinking about it and I really can't think of a difference between a parameterless constructor and one with parameters that would justify this restriction on the new constraint. What am I missing? Thanks a lot

@Eric:让我和你一起去这里:

@Eric: Let me go here with you for a sec:

构造函数就是方法

那么我想如果我这样做的话,没有人会反对:

Then I suppose no one would object if I'd go like this:

public interface IReallyWonderful
{
    new(int a);

    string WonderMethod(int a);
}

但是一旦我有了它,我就会去:

But once I have that, then I'd go:

public class MyClass<T>
        where T : IReallyWonderful
{
    public string MyMethod(int a, int b)
    {
        T myT = new T(a);
        return myT.WonderMethod(b);
    }
}

这就是我最初想做的事情.所以,抱歉,但不,构造函数不是方法,或者至少不完全是.

Which is what I wanted to do in the first place. So, sorry, but no, constructors are not methods, or at least not exactly.

关于实施此功能的困难,我真的不知道,即使我知道,我也不会对有关明智地支出股东资金的决定发表任何意见.像这样的事情,我会立即标记为答案.

On the difficulties of implementing this feature, well I'd really wouldn't know, and even if I did, I wouldn't have anything to say on a decision regarding the wisely expenditure of shareholder money. Something like that, I would've marked as an answer right away.

从学术(我的)角度来看,也就是说,不考虑实施成本,问题确实是(我在过去几个小时内总结了这一点):

From an academic (my) point of view, and that is, without any regards for implementation costs, the question really is (I've rounded it up to this in the last few hours):

构造函数应该被视为类实现的一部分,还是语义契约的一部分(就像接口被视为语义契约一样).

Should constructors be considered as part of the implementation of a class, or as part of the semantic contract (in the same way an interface is considered a semantic contract).

如果我们将构造函数视为实现的一部分,那么,约束泛型类型参数的构造函数并不是一件非常通用的事情,因为这会将泛型类型绑定到具体的实现中,而且几乎可以说为什么要使用泛型?

If we consider constructors as part of the implementation, then, constraining the constructor of a generic type parameter is not a very generic thing to do, since that'd be tying up your generic type to a concrete implementation, and one almost could say why use generics at all?

作为实现一部分的构造函数示例(将以下任何构造函数指定为由 ITransformer 定义的语义契约的一部分是没有意义的):

Example of constructor as part of the implementation (no sense in specifying any of the following constructors as part of the semantic contract defined by 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));
    }
}

问题是构造函数也可以被视为语义契约的一部分,如下所示:

The problem is that constructors may also be considered as part of the semantic contract, like so:

public interface ICollection<T> : IEnumerable<T>
{
    new(IEnumerable<T> tees);

    void Add(T tee);

    ...
}

这意味着,从一系列元素构建一个集合总是可能的,对吗?这将成为语义契约的一个非常有效的部分,对吗?

This means, it's always posible to build a collection from a sequence of elements, right? And that would make a very valid portion of a semantic contract, right?

我,在不考虑有关明智地支出股东资金的任何方面的情况下,倾向于允许构造函数作为语义合同的一部分.一些开发人员把它搞砸了,将某种类型限制为具有语义不正确的构造函数,那么,同一个开发人员添加语义不正确的操作有什么区别?毕竟,语义契约就是这样,因为我们都同意它们是,而且因为我们都很好地记录了我们的库 ;)

Me, without taking into account any of the aspects regarding the wisely expenditure of shareholder money, would favour allowing constructors as parts of semantic contracts. Some developer messes it up and constraints a certain type to having a semantically incorrect constructor, well, what's the difference there from the same developer adding a semantically incorrect operation? After all, semantic contracts are that, because we all agreed they are, and because we all document our libraries really well ;)

@supercat 正在尝试设置一些示例(引用自评论)

@supercat is been trying to set some examples as how (quote from a comment)

也很难准确定义构造函数约束应该如何工作,而不会导致令人惊讶的行为.

但我真的不同意.在 C# 中(好吧,在 .NET 中)诸如如何让企鹅飞起来?"之类的惊喜.根本不会发生.关于编译器如何解析方法调用有非常简单的规则,如果编译器无法解析它,那么它不会通过,也不会编译.

but I really must disagree. In C# (well, in .NET) surprises like "How to make a penguin fly?" simply don't happen. There are pretty straightforward rules as to how the compiler resolves method calls, and if the compiler can't resolve it, well, it won't pass, won't compile that is.

他的最后一个例子是:

如果它们是逆变的,那么如果泛型类型具有约束 new(Cat, ToyotaTercel),而实际类型只有构造函数 new(Animal, ToyotaTercel) 和 new(猫,汽车).

好吧,让我们试试这个(我认为这与@supercat 提出的情况类似)

Well, lets try this (which in my opinion is a similar situation to that proposed by @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
{ }

而且,哇,它不会编译出错

And, wow, it won't compile with the error

以下方法或属性之间的调用不明确:TestApp.Program.FunnyMethod(TestApp.Animal, TestApp.ToyotaTercel)"和TestApp.Program.FunnyMethod(TestApp.Cat, TestApp.Automobile)"

The call is ambiguous between the following methods or properties: 'TestApp.Program.FunnyMethod(TestApp.Animal, TestApp.ToyotaTercel)' and 'TestApp.Program.FunnyMethod(TestApp.Cat, TestApp.Automobile)'

如果同样的问题从带有参数化构造函数约束的解决方案中引起,我不明白为什么结果应该不同,如下所示:

I don't see why the result should be different if the same probleme arouse out of a solution with parameterized constructor constraints, like so:

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)
{ }

现在,当然,编译器无法处理构造函数上的约束,但如果可以,为什么错误不会出现在 GenericClass 行上?gc = new GenericClass<FunnyClass>(); 类似于在尝试编译第一个示例时获得的,FunnyMethod.

Now, of course, the compiler can't handle the constraint on the constructor, but if it could, why could't the error be, on the line GenericClass<FunnyClass> gc = new GenericClass<FunnyClass>(); similar to that obtained when trying to compile the first example, that of the FunnyMethod.

无论如何,我会更进一步.当重写抽象方法或实现定义在接口上的方法时,需要使用完全相同的参数类型,不允许继承者或祖先.因此,当需要参数化构造函数时,应该使用精确的定义来满足要求,而不是其他任何东西.在这种情况下,对于 GenericClass 类的泛型参数类型,永远不能将 FunnyClass 类指定为类型.

Anyway, I'd go one step further. When one overrides an abstract method or implements a method defined on an interface, one is required to do so with exactly the same parameters type, no inheritors or ancestors allowed. So, when a parameterized constructor is required, the requirement should be met with an exact definition, not with anything else. In this case, the class FunnyClass could never be specified as the type, for the generic parameter type of class GenericClass.

推荐答案

Kirk Woll 引用我的话当然是所有需要的理由;我们不需要为的功能提供理由.功能具有巨大的成本.

Kirk Woll's quote from me of course is all the justification that is required; we are not required to provide a justification for features not existing. Features have enormous costs.

然而,在这种特定情况下,我当然可以给你一些理由,如果它作为该语言未来版本的可能特性出现在设计会议上,我为什么会推迟该特性.

However, in this specific case I can certainly give you some reasons why I would push back on the feature if it came up in a design meeting as a possible feature for a future version of the language.

首先:考虑更通用的功能.构造函数是方法.如果你希望有一种方法可以说类型参数必须有一个接受 int 的构造函数",那么为什么说类型参数必须有一个名为 Q 的公共方法,它接受两个整数并返回一个字符串?"

To start with: consider the more general feature. Constructors are methods. If you expect there to be a way to say "the type argument must have a constructor that takes an int" then why is it not also reasonable to say "the type argument must have a public method named Q that takes two integers and returns a string?"

string M<T>(T t) where T has string Q(int, int)
{
    return t.Q(123, 456);
}

这是否让您觉得这是一件非常通用的事情?拥有这种约束似乎与泛型的想法背道而驰.

Does that strike you as a very generic thing to do? It seems counter to the idea of generics to have this sort of constraint.

如果该特性对于方法来说是一个坏主意,那么为什么对于方法恰好是构造函数来说它是一个想法?

If the feature is a bad idea for methods, then why is it a good idea for methods that happen to be constructors?

相反,如果方法和构造函数是个好主意,那为什么要止步于此呢?

Conversely, if it is a good idea for methods and constructors, then why stop there?

string M<T>(T t) where T has a field named x of type string
{
    return t.x;
}

我说我们要么做整个功能,要么根本不做.如果能够将类型限制为具有特定的构造函数很重要,那么让我们做整个功能,并在 一般成员的基础上限制类型,而不仅仅是 >构造函数.

I say that we should either do the whole feature or don't do it at all. If it is important to be able to restrict types to have particular constructors, then let's do the whole feature and restrict types on the basis of members in general and not just constructors.

该功能的设计、实施、测试、记录和维护成本当然要高得多.

That feature is of course a lot more expensive to design, implement, test, document and maintain.

第二点:假设我们决定实现该功能,只是构造函数"版本或任何成员"版本.我们生成什么代码?通用代码生成器的特点是它经过精心设计,因此您可以一次进行静态分析并完成它.但是在 IL 中没有描述调用采用 int 的构造函数"的标准方法.我们要么向 IL 添加一个新概念,要么生成代码以便通用构造函数调用使用反射.

Second point: suppose we decided to implement the feature, either the "just constructors" version or the "any member" version. What code do we generate? The thing about generic codegen is that it has been carefully designed so that you can do the static analysis once and be done with it. But there is no standard way to describe "call the constructor that takes an int" in IL. We would have to either add a new concept to IL, or generate the code so that the generic constructor call used Reflection.

前者很贵;改变 IL 中的一个基本概念是非常昂贵的.后者是 (1) 慢,(2) 装箱参数,以及 (3) 是您可以自己编写的代码.如果您打算使用反射来查找构造函数并调用它,那么编写使用反射查找构造函数并调用它的代码.如果这是代码生成策略,那么约束赋予的唯一好处是传递没有公共构造函数的类型参数的错误在编译时而不是运行时被捕获.您不会获得泛型的任何其他好处,例如避免反射和拳击惩罚.

The former is expensive; changing a fundamental concept in IL is very costly. The latter is (1) slow, (2) boxes the parameter, and (3) is code that you could have written yourself. If you're going to use reflection to find a constructor and call it, then write the code that uses reflection to find a constructor and call it. If this is the code gen strategy then the only benefit that the constraint confers is that the bug of passing a type argument that does not have a public ctor that takes an int is caught at compile time instead of runtime. You don't get any of the other benefits of generics, like avoiding reflection and boxing penalties.

这篇关于限制泛型类型参数具有特定的构造函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆