Непонятки с преобразованием генерического типа и конкретного типа в C#
От: техно  
Дата: 14.07.13 12:16
Оценка:
Замучился решать проблему. Хочу написать генерический метод, который принимает массив и возвращает результат (например, сумму элементов). Но важно, чтобы массив обрабатывался как конкретный тип, поскольку обработка и операции могут зависеть явно от типа. Поэтому я проверяю тип параметра T (int, double,...) и далее делаю обработку для конкретного типа уже без генериков:

    public class Processor<T> {

        public T process(T[] values) {
            if(typeof(T) == typeof(int)) { // Далле T есть int
                int agg = 0;
                int[] array = (int[]) values;   // 1. *** НЕ РАБОТАЕТ ***
                for (int i = 0; i < array.Length; i++) agg += array[i];
                agg /= array.Length;
                return (T) agg;                 // 2. *** НЕ РАБОТАЕТ ***
            }
            if (typeof(T) == typeof(double)) { // Здесь T есть double
                // Быстрая обработка double без генериков
            }
            else {
                throw new NotImplementedException("Пардон.");
            }

        }
    }


Но проблема в том, что компилятор ругается при преобразовании: когда из T[] делаем int[] (хотя мы знаем точно что это int[]), а также при возврате результата int в виде T (тут вообще не ясно откуда проблема).

Я поискал и нашел, что массив можно преобразовать с помощью (int[])Convert.ChangeType(values, typeof(int[])), но ведь это реальное преобразование всех элементов, а потому долго. Хочется просто изменить тип переменной, поскольку мы знаем какой типа она имеет. С возвратом int в виде T вообще не ясно что он хочет. Может C# вообще это не поддерживает. Буду благодарен за советы.
Re: Непонятки с преобразованием генерического типа и конкретного типа в C#
От: mogikanin Россия  
Дата: 14.07.13 12:27
Оценка:
Здравствуйте, техно, Вы писали:

Т>Замучился решать проблему. Хочу написать генерический метод, который принимает массив и возвращает результат (например, сумму элементов). Но важно, чтобы массив обрабатывался как конкретный тип, поскольку обработка и операции могут зависеть явно от типа. Поэтому я проверяю тип параметра T (int, double,...) и далее делаю обработку для конкретного типа уже без генериков:


Если у вас обработка и операции зависят явно от типа, почему не написать проще?

public int Process(int[] values)
{
}

public double Process(double[] values)
{
}
Re[2]: Непонятки с преобразованием генерического типа и конкретного типа в C#
От: техно  
Дата: 14.07.13 12:40
Оценка:
Здравствуйте, mogikanin, Вы писали:

M>Здравствуйте, техно, Вы писали:


Т>>Замучился решать проблему. Хочу написать генерический метод, который принимает массив и возвращает результат (например, сумму элементов). Но важно, чтобы массив обрабатывался как конкретный тип, поскольку обработка и операции могут зависеть явно от типа. Поэтому я проверяю тип параметра T (int, double,...) и далее делаю обработку для конкретного типа уже без генериков:


M>Если у вас обработка и операции зависят явно от типа, почему не написать проще?


M>
M>public int Process(int[] values)
M>{
M>}

M>public double Process(double[] values)
M>{
M>}
M>


Если просто, то я не могу это изменить, поскольку дизайн системы уже существует. Если более сложно, то система работает с базовыми типами (предполагается, что их можно добавлять по мере развития и необходимости). Многие операции зависят от типа (иногда сильно, иногда не очень). Если реализовать без генериков, то по всей системе будут разбросаны ветвители (switch или if-else) по типу данных. Хочется упростить дизайн и один способ это генерики. Здесь ветвление тоже не избежать, но они будут спрятаны. Может есть другой способ, например, для каждого типа создать свой детский класс и далее виртуальные методы. Это тоже спрячет ветвление, но я как-то не думал об этом (опять же, поскольку дизайн уже существует).

Но это все дизайн. А я рассматриваю эту проблемы чисто как языковую: что может предложить C# в этом смысле.
Re[3]: Непонятки с преобразованием генерического типа и конкретного типа в C#
От: mogikanin Россия  
Дата: 14.07.13 12:53
Оценка: 2 (1) +1
Здравствуйте, техно, Вы писали:

Т>Но это все дизайн. А я рассматриваю эту проблемы чисто как языковую: что может предложить C# в этом смысле.

Кастить элементы к нужному типу.
 public class Processor<T>
    {
        public T process(T[] values)
        {
            if (typeof(T) == typeof(int))
            {
                var agg = 0;
                foreach (var value in values.Cast<int>())
                {
                    agg += value;
                }
                agg /= values.Length;
                return new[] {agg}.Cast<T>().First();
            }

            return default(T);
        }
    }
Re: Непонятки с преобразованием генерического типа и конкретного типа в C#
От: samius Япония http://sams-tricks.blogspot.com
Дата: 14.07.13 12:58
Оценка: 3 (1) +2
Здравствуйте, техно, Вы писали:

Т>Но проблема в том, что компилятор ругается при преобразовании: когда из T[] делаем int[] (хотя мы знаем точно что это int[]), а также при возврате результата int в виде T (тут вообще не ясно откуда проблема).

1. (int[])(object)values;

Т>Я поискал и нашел, что массив можно преобразовать с помощью (int[])Convert.ChangeType(values, typeof(int[])), но ведь это реальное преобразование всех элементов, а потому долго. Хочется просто изменить тип переменной, поскольку мы знаем какой типа она имеет. С возвратом int в виде T вообще не ясно что он хочет. Может C# вообще это не поддерживает. Буду благодарен за советы.

2. (T)Convert.ChangeType(agg, typeof(T));

А вообще можно сделать не уступающую по времени обработку в генерическом (слово понравилось) стиле.

T Process<T, TProcessor>(T[] values, TProcessor proc)
  where TProcessor: IProcessor<T>
{
    var s = (default)T;
    for (int i = 0; i < values.Length; i++)
       s = proc.Add(s, values[i])
   
    return proc.Div(s, values.Length);
}

interface IProcessor<T>
{
    T Add(T, T);
    T Div(T, int); // надо уметь делить на целое, т.к. длина массива - целая величина
}

struct IntProcessor : IProcessor<int>
{
    public int Add(int a, int b) { return a + b; }
    public int Div(int a, int b) { return a / b; }
}

T Process<T>(T[] values)
{
   return Process(values, Processor<T>.Default);
}

Как реализовать Default, можно подглядеть рефлектором в EqualityComparer<T>.Default. Принцип тот же.

if typeof(T) == typeof(int)
   ...
Re[4]: Непонятки с преобразованием генерического типа и конкретного типа в C#
От: техно  
Дата: 14.07.13 13:37
Оценка:
Здравствуйте, mogikanin, Вы писали:

M>Здравствуйте, техно, Вы писали:


Т>>Но это все дизайн. А я рассматриваю эту проблемы чисто как языковую: что может предложить C# в этом смысле.

M>Кастить элементы к нужному типу.
M>
M> public class Processor<T>
M>    {
M>        public T process(T[] values)
M>        {
M>            if (typeof(T) == typeof(int))
M>            {
M>                var agg = 0;
M>                foreach (var value in values.Cast<int>())
M>                {
M>                    agg += value;
M>                }
M>                agg /= values.Length;
M>                return new[] {agg}.Cast<T>().First();
M>            }

M>            return default(T);
M>        }
M>    }
M>


Спасибо. Работает. Правда оказалось, что цикл

foreach (var value in values.Cast<int>())

работает в 3 раза медленнее, чем

var array = (int[])(object)values;
for (int i = 0; i < array.Length; i++)

Видимо, что-то мешает JIT'у соптимизировать код для обработки массива, а может еще чего.
Re[2]: Непонятки с преобразованием генерического типа и конкретного типа в C#
От: техно  
Дата: 14.07.13 13:51
Оценка:
Здравствуйте, samius, Вы писали:

S>Здравствуйте, техно, Вы писали:


Т>>Я поискал и нашел, что массив можно преобразовать с помощью (int[])Convert.ChangeType(values, typeof(int[])), но ведь это реальное преобразование всех элементов, а потому долго. Хочется просто изменить тип переменной, поскольку мы знаем какой типа она имеет. С возвратом int в виде T вообще не ясно что он хочет. Может C# вообще это не поддерживает. Буду благодарен за советы.

S>2. (T)Convert.ChangeType(agg, typeof(T));

Получается, что вернуть примитивный тип можно только через боксовый объект? В целом, это подходит, но чисто теоретически интересно, можно ли вернуть int как T без боксирования.

S>А вообще можно сделать не уступающую по времени обработку в генерическом (слово понравилось) стиле.


За дизайн спасибо. Я в целом думал что-такое сделать, чтобы разнести обработчики по иерархии классов. Единственный момент, это цикл по массиву T[] будет наверное долгим, поскольку для каждого элемента надо будет (виртуальный) метод вызывать. Но его можно будет легко задвинуть внутрь конкретных классов, так что это не проблема.
Re[3]: Непонятки с преобразованием генерического типа и конкретного типа в C#
От: samius Япония http://sams-tricks.blogspot.com
Дата: 14.07.13 14:03
Оценка: 2 (1) +1
Здравствуйте, техно, Вы писали:

Т>Здравствуйте, samius, Вы писали:


Т>Получается, что вернуть примитивный тип можно только через боксовый объект? В целом, это подходит, но чисто теоретически интересно, можно ли вернуть int как T без боксирования.

Чисто теоретически — нельзя. На стадии компиляции результат имеет тип T, а возвращаемое значение — int. Это несовместимые типы. А совместимыми они становятся только в рантайме в ветке if.

S>>А вообще можно сделать не уступающую по времени обработку в генерическом (слово понравилось) стиле.


Т>За дизайн спасибо. Я в целом думал что-такое сделать, чтобы разнести обработчики по иерархии классов. Единственный момент, это цикл по массиву T[] будет наверное долгим, поскольку для каждого элемента надо будет (виртуальный) метод вызывать. Но его можно будет легко задвинуть внутрь конкретных классов, так что это не проблема.

Если все сделать правильным и правильно замерять (в релизе, запущенном не из под студии или через Ctrl+F5), то долгим не будет. Я экспериментировал с подобыными вещами, обнаружил разницу, выражаемую в милисекундах лишь на многомиллионных итерациях.
Даже виртуального вызова удается избежать. Я не зря написал что IntProcessor — это struct, т.е. Value объект. Интерфейс там нужен лишь для того что бы обеспечить на стадии компиляции существование нужных методов. Но раз параметр типа Value, то компилятор решает что вызывать те самые методы можно напрямую без боксирования и vtbl.

Минус у подхода один: расчеты сложнее вычисления среднего арифметического с ним делать безобразно муторно.
Re: Непонятки с преобразованием генерического типа и конкретного типа в C#
От: nikov США http://www.linkedin.com/in/nikov
Дата: 15.07.13 01:13
Оценка: 3 (1)
Здравствуйте, техно, Вы писали:

Т>Но проблема в том, что компилятор ругается при преобразовании: когда из T[] делаем int[] (хотя мы знаем точно что это int[]), а также при возврате результата int в виде T (тут вообще не ясно откуда проблема).


Похожая ситуация рассматиривается в спецификации: 6.2.7 Explicit conversions involving type parameters

The above rules do not permit a direct explicit conversion from an unconstrained type parameter to a non-interface type, which might be surprising. The reason for this rule is to prevent confusion and make the semantics of such conversions clear. For example, consider the following declaration:

class X<T>
{
    public static long F(T t) {
        return (long)t;                // Error 
    }
}


If the direct explicit conversion of t to int were permitted, one might easily expect that X<int>.F(7) would return 7L. However, it would not, because the standard numeric conversions are only considered when the types are known to be numeric at binding-time. In order to make the semantics clear, the above example must instead be written:

class X<T>
{
    public static long F(T t) {
        return (long)(object)t;        // Ok, but will only work when T is long
    }
}

This code will now compile but executing X<int>.F(7) would then throw an exception at run-time, since a boxed int cannot be converted directly to a long.


Возможные workarounds:

int[] array = (int[])(Array)values;

или
int[] array = values as int[];
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.