在非泛型接口中使用泛型集合

本文关键字:泛型 集合 泛型接口 | 更新日期: 2023-09-27 18:31:44

我今天花了很长时间寻找解决这个问题的方法,但没有运气。我有一个泛型类,如下所示:

public class StagedEntity<T>: IStagedEntity where T : IStagedItem {
  public Type EntityType {
    get { return typeof( T ); }
  }
  public IList<T> StagedItems { get; set; }
}

以便我可以创建各种任意类型的暂存实体的集合,即:

var stagedEntities = new List<IStagedEntity> {
  new StagedEntity<StagedBrand> {
    StagedItems = new List<StagedBrand> {
      new StagedBrand {
        Code = "Brand1"
      },
      new StagedBrand {
        Code = "Brand2"
      }
    }
  },
  new StagedEntity<StagedModel> {
    StagedItems = new List<StagedModel> {
      new StagedModel {
        Name = "Model1"
      },
      new StagedBrand {
        Name = "Model2"
      }
    }
  }
}

然后,我希望在 stagedEntities 中迭代每个实体,并且对于每个实体,我需要能够访问其 StagedItems 集合中每个项目的所有属性值,如下所示:

private void DoStuff( IStagedEntity stagedEntity ) {
  var properties = stagedEntity.EntityType.GetProperties().ToList();
  foreach ( var item in stagedEntity.StagedItems ) {   <=== This line doesn't compile
    foreach ( var property in properties ) {
      // Do something with
      // property.GetValue( item, null );
    }
  }
}

但是上面的代码无法编译,因为接口定义IStagedEntity没有定义IList<T> StagedItems,我不能将其放在接口定义中,因为接口是非通用的,因此不知道T是什么。

我已经尝试了各种方法,例如使接口通用(IStagedEntity<T>)和将StagedEntities定义为new List<IStagedEntity<IStagedItem>>,我可以通过将StagedEntity<T>的每个实例转换为IStagedEntity<IStagedItem>来编译它,但是这在运行时失败了:

Unable to cast object of type
'DataExport.StagedEntity`1[DataExport.StagedBrand]' to type
'DataExport.IStagedEntity`1[DataExport.IStagedItem]'.

有没有我错过的明显解决方法?任何帮助将不胜感激。


编辑:为了完整起见,目前的IStagedEntity界面如下所示:

public interface IStagedEntity {
  Type EntityType { get; }
}

在非泛型接口中使用泛型集合

如果你可以更改DoStuff()的方法签名,我建议保持你的接口和类不变,只是像这样改变参数:

private void DoStuff<T>(StagedEntity<T> stagedEntity) // note the arg type
{
  var properties = stagedEntity.EntityType.GetProperties().ToList();
  foreach ( var item in stagedEntity.StagedItems ) {
    foreach ( var property in properties ) {
      // Do something with
      // property.GetValue( item, null );
    }
  }
}

因此,您可以像这样调用该方法:

private void foo()
{
    StagedEntity<MyGenericType> entities = null; // ...
    DoStuff(entities);
}

使用动态将获得您要求的结果。另一方面,我会考虑其他建议的方法,因为您不知道是否所有 IStagedEntity 都满足 StagedItems 属性

private static void DoStuff(IStagedEntity stagedEntity)
{
        dynamic se = stagedEntity;
        var properties = stagedEntity.EntityType.GetProperties().ToList();
        foreach (var item in se.StagedItems)
        {   
            foreach (var property in properties)
            {
             // Do something with
             // property.GetValue( item, null );
            }
        }
}

我设法通过使IStagedEntity接口通用来使其工作,以便我可以在其中包含IList<T> StagedItems - 我之前尝试过,但至关重要的是,我现在还制作了接口类型参数协变 ( <out T> ),这解决了我之前遇到的转换问题:

public interface IStagedEntity<out T> where T : IStagedItem {
  Type EntityType { get; }
  IList<T> StagedItems { get; }
}

显然,我还必须修改所有对IStagedEntity的引用,以IStagedEntity<IStagedItem>,并且这样做后,赋值(简化如下)现在可以毫无问题地工作:

var stagedEntities = new List<IStagedEntity<IStagedItem>> {
  new StagedEntity<StagedBrand>(),
  new StagedEntity<StagedModel>()
}

现在界面中的IList<T> StagedItems,我可以愉快地以我的消费方式访问此属性。

谢谢大家的帮助:o)


编辑:可能值得指出的是,如果接口上存在带有setter的泛型属性,则如上所述添加协变out将导致编译器抱怨"无效方差"。简单地删除二传手就可以解决这个问题(当然,你仍然可以在具体实现上拥有二传手)。