【问题标题】:How to convert an object of a class to an object of type T using C# generics?如何使用 C# 泛型将类的对象转换为 T 类型的对象?
【发布时间】:2014-05-22 06:40:45
【问题描述】:

我有一些应该生成图表的类。我有三个类为图表系列使用不同类型的数据。我将只提供这些类的属性,以避免混淆:)

public class PointBase
{
    public decimal X { get; set; }
}

public class Point : PointBase
{
    public decimal Y { get; set; }
}

public class Bubble : Point
{
    public decimal Size { get; set; }
}

这三个类被用作图表系列中的基本数据。图表系列在其他三个类(图表类)中表示为 Dictionary 对象。为了专注于具体问题,我不会提供图表类的代码。我将只提供类的名称:

  1. CoordinateChart(使用 PointBase 作为基本数据)。
  2. 散点图(使用点)。
  3. BubbleChart(使用 Bubble)。

我有以下方法对我的所有图表类执行完全相同的算法。这就是为什么我决定使用泛型来制作该方法。

    public Dictionary<string, T[]> GenerateSeriesDataDictionary<T>(string[] series, string[] categories) where T : PointBase, new()
    {
        // Create a new Dictionary.
        Dictionary<string, T[]> seriesData = new Dictionary<string, T[]>();

        // Gets the number of categories and assigns the categoriesNumber variable with the returned value.
        int categoriesNumber = categories.Length;

        foreach (string serie in series)
        {
            Dictionary<string, T> categoriesData = (Dictionary<string, T>)GetCategoriesData(serie);

            // Create a new array of Ts that will store the data for the current serie.
            T[] dataArray = new T[categories.Length];

            for (int i = 0; i < categoriesNumber; i++)
            {
                // If the serie contains a value for the current category, store it in the array.
                // Otherwise, set the value of the current element of the array to the default one.
                dataArray[i] = categoriesData.ContainsKey(categories[i]) ? categoriesData[categories[i]] : new T();
            }

            // Add the dataArray to the seriesData Dictionary.
            seriesData.Add(serie, dataArray);
        }

        return seriesData;
    }

所以,上面的方法接收到两个字符串数组。第一个代表系列的名称,例如“东京”和“伦敦”。第二个代表图表的类别名称——例如月份的名称——“一月”、“二月”等。

这个方法应该创建一个 Dictionary 对象,它使用一个系列的名称作为键和一个相应的对象数组,代表每个类别的数据(在我的例子中 - 每个月)。例如,字典中的单个键值对应如下所示: 关键字:“伦敦”;值:17、16、20、25(假设数据代表每个月的最高温度)。

每当我创建折线图、散点图或气泡图(由 CoordinateChart、ScatterChart 和 BubbleChart 类表示)时,我基本上都在执行相同的算法。唯一不同的是我正在生成的字典的类型。

  1. CoordinateChart 使用 Dictionary(string, PointBase[])
  2. ScatterChart 使用 Dicitonary(string, Point[])
  3. BubbleChart 使用 Dictionary(string, Bubble[])

我将 '' 字符替换为 '(' 和 ')',因为它们似乎消失了 :)

这就是为什么我决定将应该生成 Dictionary 对象的逻辑放在一个单独的方法中,然后在每个类中覆盖这个方法。

我的方法是这样的:

    public virtual Dictionary<string, PointBase> GetCategoriesData(string serie) 
    {
        Dictionary<string, PointBase> categoriesData = sqlResultDataTable.AsEnumerable()
            .Where(row => row["City"].ToString() == serie)
            .Select(row => new KeyValuePair<string, PointBase>(row["Month"].ToString(), new PointBase(decimal.Parse(row["Temperature"].ToString()))))
            .ToDictionary(item => item.Key, item => item.Value);

        return categoriesData;
    }

此方法基本上获取表示系列(例如,“伦敦”)的“月份”和“温度”值的所有键值对。

所以,我这样做是为了实现代码的可重用性。否则,我可以在我的所有三个类中执行相同的操作,并且可以在每个类中提供 GenerateSeriesDataDictionary 方法的单独实现,但我认为这不是一个好方法。

一切正常,但我在这一行得到一个错误:

Dictionary<string, T> categoriesData = (Dictionary<string, T>)GetCategoriesData(serie);

错误消息指出:'Cannot convert type Dictionary(string, PointBase) to Dictionary(string, T)'。

我将 '' 字符替换为 '(' 和 ')',因为它们似乎消失了 :)

我能做什么?

【问题讨论】:

  • 为什么不能在需要使用集合的时候循环遍历 categoriesData 字典并使用 .Value is Point 或 .Value is Bubble 匹配值?

标签: c# generics inheritance polymorphism code-reuse


【解决方案1】:

使用泛型类型参数创建基类/接口。

public abstract class Chart<T> where T : PointBase, new()
{
    public abstract IDictionary<string, T> GetCategoriesData(string serie);

    public IDictionary<string, T[]> GenerateSeriesDataDictionary<T>(string[] series, string[] categories)
    {
        // Call GetCategoriesData, etc.
    }
}

然后创建具有特定点类型的派生类:

public class ScatterChart : Chart<Point>
{
    public override IDictionary<string, Point> GetCategoriesData(string serie)
    {
        // Create new Point and set its properties.
        // We know it is a Point because we specified it in the class generic type.
    }

    // ...
}

public class BubbleChart : Chart<Point>
{
    public override IDictionary<string, Bubble> GetCategoriesData(string serie)
    {
        // Create new Bubble and set its properties.
        // We know it is a Bubble because we specified it in the class generic type.
    }

    // ...
}

【讨论】:

  • 这对于基类(CoordinateChart)来说是可以的。但我希望从 CoordinateChart 中初始化的 ScatterChart 返回 Dictionary(string, Point)。我想在 GetCategoriesData 方法中设置 Point 的 Y 属性,就像设置 X 属性一样。那么,我该如何实现呢?
  • 啊,在这种情况下,您需要让 Chart 类本身派生自通用接口或类。然后每个类都可以指定其覆盖类型。给我一些时间来编辑我的答案。
【解决方案2】:

您不能将Dictionary&lt;string, PointBase&gt; 转换为例如Dictionary&lt;string, Bubble&gt;,这就是您的代码无法编译的原因。它们不是同一件事。

您要么需要将 categoriesData 声明为 Dictionary&lt;string, PointBase&gt;,要么将 GetCategoriesData() 方法设为泛型。

【讨论】:

    【解决方案3】:

    这是variance 类型的问题,更具体的说是协方差。

    .Net 让你只做一个安全的协方差,否则就意味着

    Dictionary<string, Bubble> bubbles = GetBubbles();
    Dictionary<string, PointBase> points = bubbles;
    
    points["London"] = new Point(); //crash, not a Bubble
    

    考虑使用IEnumerable,它是一个具有协变泛型类型的只读接口。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-01
      • 2013-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多