简体   繁体   English

上载到接口 <Foo> 当类实现通用接口时 <T> 类型约束为T = Foo?

[英]Upcasting to an Interface<Foo> when a class implements generic Interface<T> with type constraint T=Foo?

I've got an interface declaring a few methods (in C#): 我有一个接口声明了一些方法(在C#中):

internal interface ILetter<T> where T:IEntity
{
    List<T> GetRecords();
    DocumentParameters GetDocumentParameters(T record);
    void MarkRecordAsHandled(T record, bool letterSent);
}

I also have an abstract class implementing the above interface, adding a bunch of general functionality as well. 我也有一个实现上述接口的抽象类,还添加了一堆常规功能。 It looks something like this: 看起来像这样:

abstract class AbstractLetter<T> : ILetter<T> where T:IEntity
{
    public abstract List<T> GetRecords();

    public abstract DocumentParameters GetDocumentParameters(T record);

    public abstract void MarkRecordAsHandled(T record, bool letterSent);

    protected readonly FamisContext FamisContext = new FamisContext();

    protected bool KnownEmail(string socialSecurityNumber){ doing stuff }
}

Finally I have different classes inheriting from the above base class (AbstractLetter). 最后,我有从上述基类(AbstractLetter)继承的不同类。 They, of course, include specific implementations of the methods declared in the interface. 当然,它们包括接口中声明的方法的特定实现。
I'd like to make a batch-job, creating the different kind of letters, based on the methods from the interface. 我想根据界面中的方法进行批处理工作,创建不同种类的字母。 I thought I could do this by making a list and then iterate through the list using the same methods on each element. 我以为可以通过创建一个列表,然后在每个元素上使用相同的方法遍历列表来做到这一点。 But apparently I cannot do that - at least not without explicitly type casting them. 但显然我无法做到这一点-至少在没有显式键入强制转换的情况下是不能做到的。
So, my question is: Can I do something like the below, but without the typecasting (and more letters, of course)? 所以,我的问题是:我可以做以下类似的事情,但是没有类型转换(当然还有更多的字母)吗?

var test = new List<ILetter<IEntity>> {new JobTrainingLetter() as ILetter<IEntity>};

Well, starters, new JobTrainingLetter() as ILetter<IEntity> is not really valid, unless you mark T explictly as an " out ", that is, covariant . 好吧,对于初学者来说, new JobTrainingLetter() as ILetter<IEntity>并不是真正有效,除非您将T显式标记为“ out ”,即协变量

If you mark a type parameter out , you should also ensure that the T can be accessed only from the "output" positions. 如果将类型参数标记为out ,则还应确保只能从“输出”位置访问T

It does not really make sense to treat your letter as ILetter<IEntity> , as that would mean that you have access to methods such as: 将字母视为ILetter<IEntity>确实没有任何意义,因为这意味着您可以使用以下方法:

DocumentParameters GetDocumentParameters(IEntity record);

on an instance of ILetter<YourConcreteEntity> . ILetter<YourConcreteEntity>的实例上。 What would happen if you try GetDocumentParameters(anotherConcreteEntity) ? 如果您尝试GetDocumentParameters(anotherConcreteEntity)会发生什么? Runtime exception. 运行时异常。

I suggest you to think about your design a bit more. 我建议您多考虑一下您的设计。

I'm not fully sure I understand your question, but is this what you're after? 我不确定我是否理解您的问题,但这是您所追求的吗?

http://ideone.com/bE08rw http://ideone.com/bE08rw

In LinqPad: 在LinqPad中:

interface IEntity
{
    string Name { get; set; }
}

class FooEntity : IEntity
{
    public string Name { get; set; }

    public FooEntity()
    {
        Name = "foo";
    }
}

interface ILetter<T> where T:IEntity
{
    string EntityMetadata { get; set; }
    List<T> GetRecords();
}


abstract class AbstractLetter<T> : ILetter<T> where T:IEntity
{
    public abstract string EntityMetadata { get; set; }
    public abstract List<T> GetRecords();
}

class JobLetter : AbstractLetter<FooEntity>
{
    public override string EntityMetadata { get; set; }
    public override List<FooEntity> GetRecords() { return null; }

    public JobLetter()
    {
        var entity = new FooEntity();
        EntityMetadata = entity.Name;
    }
}

List<T> CreateLetterList<T, K>() where T : AbstractLetter<K>, new() where K : IEntity
{
    return new List<T> { new T(), };
}

void Main()
{
    var jobLetterList = CreateLetterList<JobLetter, FooEntity>();
    foreach (var letter in jobLetterList)
    {
        Console.WriteLine(letter.EntityMetadata);
    }
}

To cater for a List that stores multiple types of letters: 要满足存储多种字母的列表的需要:

abstract class AbstractLetter: ILetter<IEntity>
{
    public abstract string EntityMetadata { get; set; }
    public abstract List<IEntity> GetRecords();
}

class JobLetter<T> : AbstractLetter where T : IEntity, new()
{
    public override string EntityMetadata { get; set; }
    public override List<IEntity> GetRecords() { return null; }

    public JobLetter()
    {
        var entity = new T();
        EntityMetadata = entity.Name;
    }
}

class SubsidyLetter<T> : AbstractLetter where T : IEntity, new()
{
    public override string EntityMetadata { get; set; }
    public override List<IEntity> GetRecords() { return null; }

    public SubsidyLetter()
    {
        var entity = new T();
        EntityMetadata = entity.Name;
    }
}
List<T> CreateLetterList<T>() where T : AbstractLetter, new()
{
    return new List<T> { new T(), };
}

void Main()
{
    var jobLetterList = CreateLetterList<JobLetter<FooEntity>>();
    var subsidyLetterList = CreateLetterList<SubsidyLetter<FooEntity2>>();

    var mergedList = new List<AbstractLetter>();
    mergedList.Add(jobLetterList[0]);
    mergedList.Add(subsidyLetterList[0]);

    foreach (var letter in mergedList)
    {
        Console.WriteLine(letter.EntityMetadata);
    }
}

As you can see you will lose some type information in the base class. 如您所见,您将在基类中丢失一些类型信息。

To support what you're trying to achieve (but not necessarily the required functionality) you need valid variance on your types: 为了支持您要实现的目标(但不一定是必需的功能),您需要对类型进行有效的变化:

interface ILetter<out T> where T:IEntity
{
    string EntityMetadata { get; set; }
    IEnumerable<T> GetRecords();
}


abstract class AbstractLetter<T> : ILetter<T> where T:IEntity
{
    public abstract string EntityMetadata { get; set; }
    public abstract IEnumerable<T> GetRecords();
}

class JobLetter : AbstractLetter<FooEntity>
{
    public override string EntityMetadata { get; set; }
    public override IEnumerable<FooEntity> GetRecords() { return null; }

    public JobLetter()
    {
        var entity = new FooEntity();
        EntityMetadata = entity.Name;
    }
}

class SubsidyLetter : AbstractLetter<FooEntity2>
{
    public override string EntityMetadata { get; set; }
    public override IEnumerable<FooEntity2> GetRecords() { return null; }

    public SubsidyLetter()
    {
        var entity = new FooEntity2();
        EntityMetadata = entity.Name;
    }
}

void Main()
{
    var jobLetterList = CreateLetterList<JobLetter, FooEntity>();
    var subsidyLetterList = CreateLetterList<SubsidyLetter, FooEntity2>();
    var mergedList = new List<ILetter<IEntity>>();

    mergedList.Add(jobLetterList[0]);
    mergedList.Add(subsidyLetterList[0]);

    foreach (var letter in mergedList)
    {
        Console.WriteLine(letter.EntityMetadata);
    }
}

IEnumerable will work in place of List as it doesn't allow you to put values into it. IEnumerable可以代替List使用,因为它不允许您将值放入其中。

Your base class is defined as: 您的基类定义为:

abstract class AbstractLetter<T> : ILetter<T> where T:IEntity

The T is bound with a generic constraint, but the constraints are not part of the type signatures (and also co/contravariance is not supported for classes, it's just for interfaces, not that you have any here anyways). T绑定了一个通用约束,但是约束不是类型签名的一部分(并且类不支持co / contravariance,它仅用于接口,无论如何都不能在其中使用)。 So, your class, for the compiler, from the inheritance point of view, really looks something like *): 因此,对于类,对于编译器,从继承的角度来看,它的确类似于*):

abstract class AbstractLetter<T> : ILetter<T>

which does not tell the compiler that it implements ILetter<IEntity> . 它不会告诉编译器它实现了ILetter<IEntity>

Simply add that part explicitely: 只需显式地添加该部分:

abstract class AbstractLetter<T> : ILetter<IEntity>, ILetter<T> where T:IEntity

Of course it may force you to add some code to satisfy that extra interface (you can have VS generate them for you and fill with pass-forwards to the current code), but it will work. 当然,这可能会迫使您添加一些代码来满足该额外接口的要求(您可以让VS为您生成它们并使用对当前代码的传递填充),但是它可以工作。

EDIT: Oor, you can use variance settings on the interface like ChrisEelmaa wisely suggested (I'm suprised I didn't thought of it, even after mentioning variance, heh!). 编辑:噢,您可以像ChrisEelmaa明智地建议的那样在界面上使用差异设置(我很惊讶,即使提到差异,我也没想到这一点,嘿!)。 But that won't work directly with current method set. 但这不适用于当前方法集。 I also don't recommend creating two interfaces ILetter<int T> + ILetter2<out T> - this almost always leads to bloat and really weird and complicated code. 我也不建议创建两个接口ILetter<int T> + ILetter2<out T> -这几乎总是会导致代码膨胀,异常和复杂。 Too complicated comparing to a few casts. 与几个演员相比太复杂了。

*) whole this paragraph is a really really loose description *)整个段落是一个非常松散的描述

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

相关问题 公共抽象类 Foo<T> 其中 T : Foo<T> . 何时/为什么这有助于确保 T 实现 Foo<T> - public abstract class Foo<T> where T : Foo<T>. When/why is this useful to ensure T implements Foo<T> T实现接口的通用方法<T> - Generic method where T implements Interface<T> 如何判断类是否实现了泛型类型的接口 - How to Tell if a Class implements an interface with a generic type 泛型类型参数是实现接口的类 - Generic type parameter is an class that implements an interface 通用方法,其中 T 是实现接口的列表 - Generic Method where T is List that implements interface 使用反射创建实现T类型接口的类 - Using reflection to create a class that implements an interface of type T 具有通用类型的基类,该基类实现具有通用类型的接口 - Base class with a generic type which implements an interface with a generic type 为什么在实现接口时,通用 class 中的类型约束对于编译器来说是不够的? - Why type constraint in a generic class is not enough for the compiler when implementing an interface? 检查对象是否使用通用接口实现接口 <T> 泛型类型是T的子代 - Checking if an object implements an interface with a generic <T> where the generic type is a child of T 我可以通过IQueryable <Foo> 进入IQueryable <IFoo> 如果对象实现了该接口? - can i pass in IQueryable<Foo> into IQueryable<IFoo> if the object implements that interface?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM