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

CSJ*_*CSJ 8 c# generics type-constraints

我知道如何强制类型参数成为另一种类型的类型:

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

var mapping = MapManager.Find<Truck>();
mapping.Serialize(new TonkaTruck());
Run Code Online (Sandbox Code Playgroud)

有没有办法强制类型参数成为另一种类型的类型?

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());
Run Code Online (Sandbox Code Playgroud)

目前,我不得不在运行时使用IsSubclassOf内部比较T1和T2 IncludeMappingOf.编译安全的解决方案更可取.有任何想法吗?

编辑: 改变示例,以减少设计臭.

注意:链接的问题非常相似,但没有给出合适的答案.希望这个问题也会对这个问题有所了解.

编辑#2:

更简单的例子:

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);
Run Code Online (Sandbox Code Playgroud)

编译器:参数类型'T2'不能赋值参数类型'T1'. 是的我知道,我正在尝试让编译器只允许T2可分配给参数类型T1的情况!

Tim*_*lds 5

虽然w0lf的答案提供了直接的解决方案,但我想给出一些背景解释.

当你写的东西像

class C<A> where A : B
Run Code Online (Sandbox Code Playgroud)

要么

void F<A>() where A : B
Run Code Online (Sandbox Code Playgroud)

表单的约束A : B必须A作为声明的类,接口,方法等的泛型类型参数之一.

您面临的错误不是因为您在冒号的右侧放置了当前声明的泛型类型参数(这是合法的) - 这是因为您已经放置了外部声明的泛型类型参数(不是当前声明)在结肠的左侧.

如果要A : B在某些声明上形成约束,则A必须在该声明上引入并且范围A必须小于或等于范围B.这是一个实用的语言限制的原因是,对于任何泛型类型参数T,它将关于类型约束的任何推理隔离TT引入的单个声明.


Jef*_*ger 1

您可以使用扩展方法来接近您想要的。使用您的持有者示例,它将是:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您的示例调用代码就可以编译而不会出现错误:

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

映射示例由于它是一个接口而变得复杂。如果无法从现有接口实现扩展方法,则可能需要向接口添加实现方法。这意味着您仍然需要运行时检查,但调用者可以获得良好的语法和编译时检查。那会是这样的:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,IncludeMappingOf没有类型参数T1,因此无法推断类型参数。调用它时您必须指定两种类型:

var mapping = MapManager.Find<Truck>();
mapping.IncludeMappingOf<Truck, Vehicle>();
mapping.Serialize(new TonkaTruck());
Run Code Online (Sandbox Code Playgroud)

这通常可以通过更改 API 以包含参数(即truckMapping.IncludeMappingOf(vehicleMapping))、更改参数所在的方法/类或创建链的流畅 API(即mapping.Of<Vehicle>().Include())来解决。