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

时间:2013-09-03 15:45:33

标签: 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());

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

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());

目前,我必须使用IsSubclassOf内的IncludeMappingOf在运行时比较T1和T2。编译安全的解决方案更可取。有什么想法吗?

修改 将示例更改为更少设计臭。

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

编辑#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);

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

3 个答案:

答案 0 :(得分:5)

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

当你写

之类的东西时
class C<A> where A : B

void F<A>() where A : B

A : B形式的约束必须具有A作为声明的类,接口,方法等的泛型类型参数之一。

你所面临的错误是不是,因为你已经在冒号的右侧放置了当前声明的泛型类型参数(这是合法的) - 这是因为你放置了一个通用的冒号左侧的外部声明(不是当前声明)的类型参数。

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

答案 1 :(得分:4)

在类(接口)级别声明泛型类型和泛型约束:

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

答案 2 :(得分:1)

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

var principalMock = new Mock<IPrincipal>(); // Mock<T> is from the Moq framework
principalMock.Setup(x => x.IsAuthenticated).Returns(true); // Or false, depending on what you're testing

var httpContextMock = new Mock<HttpContextBase>();
httpContextMock.Setup(x => x.User).Returns(principalMock.Object);

var controllerContext = new ControllerContext { HttpContext = contextMock.Object };
conrollerContext.Controller = controller;
controller.ControllerContext = controllerContext;

然后允许您的示例调用代码进行编译而不会出现错误:

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

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

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

不幸的是,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)); } } 没有IncludeMappingOf类型的参数,因此无法推断出类型参数。调用它时,您必须指定这两种类型:

T1

通常可以通过更改API以包含参数(即var mapping = MapManager.Find<Truck>(); mapping.IncludeMappingOf<Truck, Vehicle>(); mapping.Serialize(new TonkaTruck()); ),更改参数所在的方法/类或创建链的流畅API(即truckMapping.IncludeMappingOf(vehicleMapping))来解决这个问题。