简体   繁体   English

将类型参数约束为基本类型

[英]Constrain type parameter to a base type

I know how to force a type parameter to be a subtype of another type: 我知道如何强制类型参数成为另一种类型的类型:

public interface IMapping<T2> 
{
    public void Serialize<T3>(T3 obj) 
        where T3 : T2;
}
...

var mapping = MapManager.Find<Truck>();
mapping.Serialize(new TonkaTruck());

Is there a way to force a type parameter to be a supertype of another type? 有没有办法强制类型参数成为另一种类型的类型?

public interface IMapping<T2>
{
    public void IncludeMappingOf<T1>() 
        where T2 : T1;   // <== doesn't work
}
...

var mapping = MapManager.Find<Truck>();

// Truck inherits Vehicle    
// Would like compiler safety here:
mapping.IncludeMappingOf<Vehicle>(); 

mapping.Serialize(new TonkaTruck());

Currently, I'm having to compare T1 and T2 at runtime using IsSubclassOf inside IncludeMappingOf . 目前,我必须在运行时使用IsSubclassOf中的IncludeMappingOf来比较T1和T2。 A compile-safe solution would be preferable. 编译安全的解决方案更可取。 Any ideas? 有任何想法吗?

EDIT: Changed the example to be less design-smelly. 编辑:改变示例,以减少设计臭。

NOTE: The linked question is quite similar, but no suitable answer is given. 注意:链接的问题非常相似,但没有给出合适的答案。 Hopefully this question will shed some light on that one as well. 希望这个问题也会对这个问题有所了解。

EDIT #2: 编辑#2:

Simpler example: 更简单的例子:

public class Holder<T2>
{
    public T2 Data { get; set; }

    public void AddDataTo<T1>(ICollection<T1> coll)
        //where T2 : T1    // <== doesn't work
    {
        coll.Add(Data);   // error
    }
}

...
var holder = new Holder<Truck> { Data = new TonkaTruck() };
var list = new List<Vehicle>();
holder.AddDataTo(list);

Compiler: Argument type 'T2' is not assignable to parameter type 'T1'. 编译器:参数类型'T2'不能赋值参数类型'T1'。 Yes I know that, I'm trying to get the compiler to allow only cases where T2 IS assignable to parameter type T1! 是的我知道,我正在尝试让编译器只允许T2可分配给参数类型T1的情况!

While w0lf's answer gives a direct solution, I want to give some background explanation. 虽然w0lf的答案提供了直接的解决方案,但我想给出一些背景解释。

When you write something like 当你写的东西像

class C<A> where A : B

or 要么

void F<A>() where A : B

the constraints of the form A : B must have A as one of the generic type parameters on the class, interface, method, etc. being declared. 形式A : B的约束必须将A作为声明的类,接口,方法等的泛型类型参数之一。

The error you are facing is not because you've placed a generic type parameter of the current declaration on the right side of the colon (that's legal) - it's because you've placed a generic type parameter of an outer declaration (not of the current declaration) on the left side of the colon. 您面临的错误不是因为您在冒号的右侧放置了当前声明的泛型类型参数(这是合法的) - 这是因为您已经放置了外部声明的泛型类型参数(不是当前声明)在结肠的左侧。

If you want to form a constraint A : B on some declaration, A must be introduced on that declaration and the scope of A must be less than or equal to the scope of B . 如果要在某个声明上形成约束A : B ,则必须在该声明中引入A ,并且A的范围必须小于或等于B的范围。 The reason this is a pragmatic language restriction is that, for any generic type parameter T , it isolates any reasoning about constraints on the type T to the single declaration where T is being introduced. 究其原因,这是一个务实的语言的限制是,对于任何泛型类型参数T ,它隔离了关于类型约束任何推理T到单个声明T被引入。

Declare both generic types and the generic constraint at class(interface) level: 在类(接口)级别声明泛型类型和泛型约束:

public interface IMapping<T1, T2> where T2 : T1
{
    void IncludeMapping(IMapping<T1, T2> otherMapping);
}

You can use extension methods to come close to what you want. 您可以使用扩展方法来接近您想要的。 Using your holder example it would be: 使用您的持有者示例将是:

public class Holder<T2>
{
    public T2 Data { get; set; }
}

public static class HolderExtensions
{
    public static void AddDataTo<T2, T1>(this Holder<T2> holder, ICollection<T1> coll)
        where T2 : T1
    {
        coll.Add(holder.Data);
    }
}

That then allows your example calling code to compile without error: 然后,允许您的示例调用代码编译而不会出现错误:

var holder = new Holder<Truck> { Data = new TonkaTruck() };
var list = new List<Vehicle>();
holder.AddDataTo(list);

The mapping example is complicated by the fact that it is an interface. 映射示例因为它是一个接口而变得复杂。 It may be necessary to add an implementation method to the interface if there is no way to implement the extension method from the existing interface. 如果无法从现有接口实现扩展方法,可能需要向接口添加实现方法。 That means you will still need a runtime check, but callers can get the good syntax and compile time checking. 这意味着您仍然需要运行时检查,但调用者可以获得良好的语法和编译时间检查。 That would be something like: 这将是这样的:

public interface IMapping<T2>
{
    void IncludeMappingOf(Type type);
}

public static class MappingExtensions
{
    public static void IncludeMappingOf<T2, T1>(this IMapping<T2> mapping)
        where T2 : T1
    {
        mapping.IncludeMappingOf(typeof(T1));
    }
}

Unfortunatly, the IncludeMappingOf does not have a parameter of type T1 so the type parameters cannot be inferred. 遗憾的是, IncludeMappingOf没有T1类型的参数,因此无法推断出类型参数。 You are forced to specify both types when calling it: 调用它时,您必须指定这两种类型:

var mapping = MapManager.Find<Truck>();
mapping.IncludeMappingOf<Truck, Vehicle>();
mapping.Serialize(new TonkaTruck());

That can often be worked around by changing the API to include a parameter (ie truckMapping.IncludeMappingOf(vehicleMapping) ), changing which method/class the parameter is on or in fluent APIs creating chains (ie mapping.Of<Vehicle>().Include() ). 通常可以通过更改API以包含参数(即truckMapping.IncludeMappingOf(vehicleMapping) ),更改参数所在的方法/类或创建链的流畅API(即mapping.Of<Vehicle>().Include() )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM