当类实现具有类型约束的通用接口<t>时,向上转换为接口<foo> T = Foo?</t> </foo>

时间:2014-11-17 10:50:12

标签: c# .net generics

我有一个界面声明了一些方法(在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>};

3 个答案:

答案 0 :(得分:3)

好吧,首发,new JobTrainingLetter() as ILetter<IEntity>并非真正有效,除非您将T明确标记为&#34; out &#34;,即{{ 3}}

如果您标记了类型参数out,则还应确保只能从&#34;输出&#34;中访问T。位置。

将您的信件视为ILetter<IEntity>并不合理,因为这意味着您可以访问以下方法:

DocumentParameters GetDocumentParameters(IEntity record);

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

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

答案 1 :(得分:1)

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

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

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

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

答案 2 :(得分:0)

您的基类定义为:

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为你生成它们并填充传递给当前代码),但它会起作用。

编辑:Oor,你可以在界面上使用方差设置,比如ChrisEelmaa明智地建议的(我很惊讶我没有想到它,即使在提到方差之后,嘿!)。但这不会直接与当前的方法集一起使用。我也不建议创建两个接口ILetter<int T> + ILetter2<out T> - 这几乎总会导致膨胀,并且代码非常奇怪和复杂。与少数演员比较复杂。

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