简体   繁体   English

如何使用C#泛型将类的对象转换为T类型的对象?

[英]How to convert an object of a class to an object of type T using C# generics?

I have some classes that are supposed to generate charts. 我有一些应该生成图表的类。 I have three classes that use different type of data for the charts' series. 我有三个类,它们为图表系列使用不同类型的数据。 I will provide just the properties of these classes, to avoid confusion :) 我将只提供这些类的属性,以避免造成混淆:)

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

These three classes are used as the basic piece of data in the series of the charts. 这三个类用作图表系列中的基本数据。 The series of the charts are represented as Dictionary objects in other three classes (the chart ones). 图表系列在其他三个类别(图表类别)中表示为Dictionary对象。 I won't provide code of the chart classes in order to focus on the specific problem. 我不会提供图表类的代码来关注特定的问题。 I will just provide the names of the classes: 我将只提供类的名称:

  1. CoordinateChart (using PointBase as a basic piece of data). CoordinateChart(使用PointBase作为基本数据)。
  2. ScatterChart (using Point). ScatterChart(使用Point)。
  3. BubbleChart (using Bubble). BubbleChart(使用Bubble)。

I have the following method that does exactly the same algorithm for all my chart classes. 我有以下方法,可以对我所有的图表类执行完全相同的算法。 That's why I decided to make the method using generics. 这就是为什么我决定使用泛型来创建该方法的原因。

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

So, the above method recieves two arrays of strings. 因此,上述方法接收两个字符串数组。 The first one represents the names of the series, for example "Tokyo" and "London". 第一个代表系列的名称,例如“东京”和“伦敦”。 The second one represents the names of the categories for the chart - for example, the names of the months - "January", "February", etc. 第二个代表图表的类别名称-例如,月份名称-“一月”,“二月”等。

This method is supposed to create a Dictionary object, that uses the name of a serie as a key and a corresponding array of objects, representing the data for each category (in my case - each month). 应该使用此方法创建一个Dictionary对象,该对象使用一个序列的名称作为键和对应的对象数组,代表每个类别的数据(在我的情况下为每个月)。 For example, a single key-value pair in the Dictionary should look something like this: Key: "London"; 例如,字典中的一个键/值对应该看起来像这样:Key:“ London”; Value: 17, 16, 20, 25 (let's say the data represents maximum temperature for each month). 值:17、16、20、25(假设数据代表每个月的最高温度)。

I'm basically executing the same algorithm, whenever I'm creating a line, a scatter or a bubble chart (represented by the CoordinateChart, ScatterChart and BubbleChart classes). 每当我创建折线图,散点图或气泡图(由CoordinateChart,ScatterChart和BubbleChart类表示)时,我基本上都在执行相同的算法。 The only thing that is different is the type of the dictionary I'm generating. 唯一不同的是我正在生成的字典的类型。

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

I replaced the '<' and '>' characters with '(' and ')', because they seem to disappear :) 我用'('和')'替换了'<'和'>'字符,因为它们似乎消失了:)

That's why I decided to put the logic that is supposed to generate the Dictionary object in a separate method and then override this method in each of these classes. 这就是为什么我决定将应该生成Dictionary对象的逻辑放在单独的方法中,然后在每个这些类中重写此方法的原因。

My method looks like this: 我的方法如下所示:

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

This method basically gets all the key-value pairs representing a "Month" and a "Temperature" value for a serie (for instance, "London"). 此方法基本上获取代表一个系列的“月”和“温度”值的所有键值对(例如“伦敦”)。

So, I'm doing all this in order to have code reusability. 因此,我正在做所有这些工作,以使代码具有可重用性。 Otherwise, I could do the same thing in all of my three classes and I could provide an individual implementation of the GenerateSeriesDataDictionary method in each one, but I don't think this is a good approach. 否则,我可以在所有三个类中做同样的事情,并且可以在每个类中提供GenerateSeriesDataDictionary方法的单独实现,但是我认为这不是一个好方法。

Everything is okay, but I get an Error on this line: 一切都很好,但是我在这条线上收到错误消息:

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

The message of the error states: 'Cannot convert type Dictionary(string, PointBase) to Dictionary(string, T)'. 错误消息指出:“无法将Dictionary(string,PointBase)类型转换为Dictionary(string,T)”。

I replaced the '<' and '>' characters with '(' and ')', because they seem to disappear :) 我用'('和')'替换了'<'和'>'字符,因为它们似乎消失了:)

What can I do? 我能做什么?

Create a base class/interface with a generic type parameter. 使用泛型类型参数创建基类/接口。

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

Then create derived classes with specific point types: 然后创建具有特定点类型的派生类:

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

    // ...
}

You cannot cast Dictionary<string, PointBase> to eg Dictionary<string, Bubble> , so that's why your code won't compile. 您不能将Dictionary<string, PointBase>Dictionary<string, Bubble> ,因此这就是您的代码无法编译的原因。 They are not the same thing. 它们不是同一件事。

You either need to declare categoriesData as a Dictionary<string, PointBase> , or make the GetCategoriesData() method generic. 您或者需要将CategoriesData声明为Dictionary<string, PointBase> ,或者使GetCategoriesData()方法通用。

This is a problem of type variance , covariance to be more specific. 这是类型差异 ,协方差更具体的问题。

.Net let's you do a safe covariance only, otherwise it would mean .Net让您只做一个安全的协方差,否则就意味着

Dictionary<string, Bubble> bubbles = GetBubbles();
Dictionary<string, PointBase> points = bubbles;

points["London"] = new Point(); //crash, not a Bubble

Consider using IEnumerable that is a readonly interface with covariant generic type. 考虑使用IEnumerable ,它是具有协变泛型类型的只读接口。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM