【问题标题】:How to add a known type to a List<T>?如何将已知类型添加到 List<T>?
【发布时间】:2019-05-28 22:09:19
【问题描述】:

这里的总体目标是这样的:我们在 Azure Blob 存储中存储了大量各种名称和格式的 CSV 文件。我们需要将它们转换为列表。

我有一个界面:

public interface IGpasData
{
    List<T> ConvertToList<T>(StreamReader reader);
}

下面是一个实现它的类的例子:

public class GpasTableOfContent : IGpasData
{
    public string TocProp0 { get; set; }
    public string TocProp1 { get; set; }
    public string TocProp2 { get; set; }

    public List<T> ConvertToList<T>(StreamReader reader)
    {
        List<T> dataList = new List<T>();
        while (!reader.EndOfStream)
        {
            var lineItem = reader.ReadLine();
            GpasTableOfContent dataItem = new GpasTableOfContent
            {
                TocProp0 = lineItem.Split(',')[0],
                TocProp1 = lineItem.Split(',')[1],
                Type = lineItem.Split(',')[2]
            };
            dataList.Add(dataItem);
        }
        return dataList;
    }
}

为了继续上面的类示例,有一个名为 ToC.csv 的文件。在一个旨在将该文件转换为列表的类中,我进行了以下调用:

List<GpasTableOfContent> gpasToCList = ConvertCloudFileToList<GpasTableOfContent>("ToC.csv", "MyModel");

其他一些可能的例子:

List<GpasFoo> gpasFooList = ConvertCloudFileToList<GpasFoo>("foo.csv", "MyModel");

List<GpasBar> gpasBarList = ConvertCloudFileToList<GpasBar>("bar.csv", "MyModel");

这是 ConvertCloudFileToList:

private List<T> ConvertCloudFileToList<T>(string fileName, string modelName)
    {
        // Get the .csv file from the InProgress Directory
        string filePath = $"{modelName}/{fileName}";
        CloudFile cloudFile = _inProgressDir.GetFileReference(filePath);

        List<T> dataList = new List<T>();

        // Does the file exist?
        if (!cloudFile.Exists())
            return dataList;

        using (StreamReader reader = new StreamReader(cloudFile.OpenRead()))
        {
            IGpasData gpasData = (IGpasData)Activator.CreateInstance<T>();
            dataList = gpasData.ConvertToList<T>(reader);
        }

        return dataList;
    }

这让我们回到ConvertToList。问题出在这里:

dataList.Add(dataItem);

无法将“GpasFoo”转换为“T”

不知道如何解决这个问题。

【问题讨论】:

  • 好的;删除了那个; T 这里是 GpasTableOfContent 的子类吗?还是这无关?
  • 不相关。嗯...如何在不使用数英里代码填满屏幕的情况下呈现大画面?让我稍微编辑一下 OP。
  • 接口被编写为支持任何类型 T,但代码被编写为显式使用 ListGpasTableOfContents。哪一个是正确的?
  • 接口正确。这是需要帮助的方法。
  • 在我看来,您应该将T 类型参数移动到接口:IGpasData&lt;T&gt; { List&lt;T&gt; ConvertToList(StreamReader reader); },然后是GpasTableOfContent : IGpasData&lt;T&gt;,尽管您不能静态确保声明和结果类型匹配。跨度>

标签: c# .net list generics


【解决方案1】:

当提供StreamReader 时,任何IGpasData 的对象都应该能够生成任何给定类型的列表。 GpasTableOfContent 不满足这个要求,它只能产生一个自己类型的列表。

但是,让一种类型的 GpasData 负责转换所有内容似乎并不合理,因此我建议将 Type 参数从 ConvertToList 方法移到接口中。这样子类将只负责转换特定类型的列表。

public interface IGpasData<T>
{
    List<T> ConvertToList(StreamReader reader);
}

public class GpasTableOfContent : IGpasData<GpasTableOfContent>
{
    //...

    public List<GpasTableOfContent> ConvertToList(StreamReader reader)
    {
        //...
    }
}

在旁注中,创建一个空目录然后使用它从流中读取并生成真实目录的列表对我来说似乎很笨拙。在我看来,创建这些内容对象的行为应该移到它自己的类中。

【讨论】:

    【解决方案2】:

    如果不提供一些额外的逻辑,您将无法在此处做您想做的事。问题是你有一个读取CSV文件的字符串,你想把它转换成T,但是没有把字符串转换成任意类型的规则。

    一种方法是将方法更改为也采用委托 Func,该委托 Func 用于将每一行转换为 T。然后,例如,如果您的数据保证由双精度数组成,则可以传递 t => Double .Parse(t) 用于该论点。当然,这种方法需要您更改正在实现的接口方法的签名。

    如果您无法更改接口方法的签名,那么我只能建议尝试处理一组预定义的类型并为其他类型抛出异常。

    【讨论】:

      【解决方案3】:

      正如其他人指出的那样,这种设计是有缺陷的:

      public interface IGpasData
      {
         List<T> ConvertToList<T>(StreamReader reader);
      }
      

      这个合同说IGpasData 应该只知道如何反序列化任何东西。这没有意义。

      IGpasData 应该知道如何反序列化自己,为此我们需要一个自引用接口:

      public interface IGpasData<T> where T : IGpasData<T>
      {
          List<T> ConvertToList(StreamReader reader);
      }
      
      public class GpasBar: IGpasData<GpasBar>
      {
          public string MyPropertyA { get; set; }
          public int MyPropertyB { get; set; }
      
          public List<GpasBar> ConvertToList(StreamReader reader)
          {
              var results = new List<GpasBar>();
              while (!reader.EndOfStream)
              {
                  var values = reader.ReadLine().Split(',');
                  results.Add(new GpasBar()
                  {
                      PropertyA = values[0],
                      PropertyB = int.Parse(values[1]),
                  });
              }
              return results;
          }
      }
      

      或者,IGpasData 应该知道如何从值数组中填充自己:

      public interface IGpasData
      {
          void Populate(string[] values);
      }
      public class GpasBar
      {
          public string MyPropertyA { get; set; }
          public int MyPropertyB { get; set; }
      
          public void Populate(string[] values)
          {
              MyPropertyA = values[0];
              MyPropertyB = int.Parse(values[1]);
          }
      }
      
      public static List<T> ConvertCloudFileToList<T>(string fileName, string modelName)
          where T : IGpasData, new()
      {
          // ...
          using (StreamReader reader = new StreamReader(cloudFile.OpenRead()))
          {
              var results = new List<T>();
              while (!reader.EndOfStream)
              {
                  var item = new T();
                  item.Populate(reader.ReadLine().Split(','));
      
                  results.Add(item);
              }
              return results;
          }
      }
      

      使用第二种方法,您可以避免重复有关StreamReader 的部分并读取行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-12
        • 1970-01-01
        • 1970-01-01
        • 2014-04-14
        相关资源
        最近更新 更多