向上转换为接口当类实现具有类型约束 T=Foo 的泛型 Interface

本文关键字:Foo 约束 类型 Interface 泛型 实现 转换 接口 | 更新日期: 2023-09-27 18:30:40

我有一个声明几个方法的接口(在 C# 中):

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

我还有一个实现上述接口的抽象类,还添加了一堆通用功能。它看起来像这样:

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

最后,我从上面的基类(AbstractLetter)继承了不同的类。当然,它们包括在接口中声明的方法的特定实现。
我想做一个批处理作业,根据界面中的方法创建不同类型的字母。我想我可以通过制作一个列表来做到这一点,然后在每个元素上使用相同的方法循环访问列表。但显然我不能这样做 - 至少在没有显式类型转换的情况下不能这样做。
所以,我的问题是:我可以做类似下面的事情,但没有类型转换(当然还有更多的字母)吗?

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

向上转换为接口<Foo>当类实现具有类型约束 T=Foo 的泛型 Interface<T> 时

好吧,初学者,new JobTrainingLetter() as ILetter<IEntity>并不真正有效,除非您明确地将T标记为"out",即协变。

如果将类型参数标记为out ,还应确保只能从"输出"位置访问T

将您的信件视为ILetter<IEntity>并没有真正的意义,因为这意味着您可以使用以下方法:

DocumentParameters GetDocumentParameters(IEntity record);

ILetter<YourConcreteEntity> 的实例上。如果你尝试GetDocumentParameters(anotherConcreteEntity)会发生什么?运行时异常。

我建议你多考虑一下你的设计。

我不完全确定我是否理解您的问题,但这就是您所追求的吗?

http://ideone.com/bE08rw

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

为了满足存储多种类型字母的列表:

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

如您所见,您将丢失基类中的一些类型信息。

为了支持您尝试实现的目标(但不一定是必需的功能),您需要对类型进行有效的差异:

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 将代替 List,因为它不允许您将值放入其中。

基类定义为:

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

T与泛型约束绑定,但约束不是类型签名的一部分(类也不支持 co/contravariance,它仅适用于接口,反正你在这里没有任何)。所以,你的类,对于编译器来说,从继承的角度来看,真的看起来像*):

abstract class AbstractLetter<T> : ILetter<T>

这不会告诉编译器它实现了ILetter<IEntity> .

只需明确添加该部分:

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

当然,它可能会迫使您添加一些代码来满足该额外的接口(您可以让 VS 为您生成它们并填充当前代码的传递),但它会起作用。

编辑:哦,你可以像ChrisEelmaa明智地建议的那样在界面上使用方差设置(我很惊讶我没有想到它,即使在提到方差之后,呵呵!但这不能直接适用于当前的方法集。我也不建议ILetter<int T> + ILetter2<out T>创建两个接口 - 这几乎总是会导致膨胀和非常奇怪和复杂的代码。与几个演员相比太复杂了。

*)整个这一段是一个非常非常松散的描述