Как преобразовать объект класса в объект типа T с использованием обобщений C #?

1

У меня есть классы, которые должны генерировать диаграммы. У меня есть три класса, которые используют разные типы данных для серии диаграмм. Я дам только свойства этих классов, чтобы избежать путаницы :)

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

Эти три класса используются в качестве основной части данных в серии диаграмм. Ряд диаграмм представлен как объекты Словаря в других трех классах (графические). Я не буду предоставлять код классов диаграмм, чтобы сосредоточиться на конкретной проблеме. Я просто укажу имена классов:

  1. CoordinateChart (используя PointBase как базовую часть данных).
  2. ScatterChart (используя точку).
  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 использует словарь (строка, PointBase [])
  2. ScatterChart использует Dicitonary (строка, точка [])
  3. BubbleChart использует словарь (строка, Bubble [])

Я заменил символы '<' и '>' на '(' and ')', потому что они, кажется, исчезают :)

Поэтому я решил поставить логику, которая должна генерировать объект 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);

В сообщении об ошибке говорится: "Невозможно преобразовать тип Словарь (строка, PointBase) в словарь (строка, T)".

Я заменил символы '<' и '>' на '(' and ')', потому что они, кажется, исчезают :)

Что я могу сделать?

Теги:
generics
polymorphism
inheritance
code-reuse

3 ответа

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

    // ...
}
  • 0
    Это нормально для базового класса (CoordinateChart). Но я хочу, чтобы ScatterChart, который наследовал от CoordinateChart, возвращал словарь (строка, точка). И я хочу установить свойство Y для Point в методе GetCategoriesData так же, как я установил свойство X. Итак, как мне этого добиться?
  • 0
    В таком случае вам нужно сделать так, чтобы классы Chart сами производились от универсального интерфейса или класса. Затем каждый класс может указать свой тип переопределения. Дайте мне несколько минут, чтобы отредактировать мой ответ.
Показать ещё 1 комментарий
0

Вы не можете использовать Dictionary<string, PointBase> например, Dictionary<string, Bubble>, поэтому ваш код не будет компилироваться. Это не одно и то же.

Вам либо нужно объявить Dictionary<string, PointBase> как Dictionary<string, PointBase>, либо сделать метод GetCategoriesData() общим.

0

Это проблема дисперсии типа, ковариация более конкретна.

.Net позволяет вам делать безопасную ковариацию, иначе это будет означать

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

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

Рассмотрим использование IEnumerable который является интерфейсом только для чтения с ковариантным родовым типом.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню