Deutsch

C# - сделать в потомке дженерика параметр типа налловым

218  
alex445 патриот4 дня назад, 22:46
NEW 4 дня назад, 22:46 
Последний раз изменено 4 дня назад, 22:46 (alex445)

Скажем, есть базовый дженерик класс для общей обработки данных разных типов


class Bace<T> // нет ограничений на тип T
{
T Value { get; set; }
}



И унаследованный класс для обработки числовых данных - тут использую интерфейс INumber для ограничения на числа.


class Derived<T> : Base<T>
where T: INumber<T>
{
}


Так вот, хочется не просто числа обрабатывать, а налловые числа. Для этого вычитал, что надо сделать ограничение ещё и на структуру


class Derived<T> : Base<T>
where T: struct, INumber<T>
{
}


Тогда можно будет переопределить поле Value как налловое и работать в нём с налловыми числами


class Derived<T> : Base<T>
where T: struct, INumber<T>
{
new T? Value { get; set; }
}


Как считаете, будет это работать?

#1 
alex445 патриот4 дня назад, 22:54
4 дня назад, 22:54 
в ответ alex445 4 дня назад, 22:46, Последний раз изменено 4 дня назад, 22:56 (alex445)

Сразу второй вопрос. Вот есть в базовом классе общая обработка для всех типов, включая ссылочные. И хочется сохранить эту обработку и в производном классе, т.к. хотя там и налловая структура, но код по сути выглядит одинаково. Разница лишь в самом типе: T или T?. Скажем, есть метод с определённым кодом (неважно какой)


class Bace<T> // нет ограничений на тип T
{
T ProcessValue(T input) {...}
}


Ну и в потомке хочу такой же по сути код, лишь сам параметр будет теперь налловой структурой, а не любым типом


class Derived<T> : Base<T>
where T: struct, INumber<T>
{
T? ProcessValue(T? input) {...} // как определить этот метод? через new, override, ещё как-то?
}


Как сделать так, чтобы код не надо было повторять, заменяя лишь тип параметра? Ведь метод ProcessValue в потомковом классе, вызванный из базового класса, не может работать с параметрами типа T? с такими ограничениями на тип (у меня в Студии компилятор выдаёт ошибку - что-то типа о несовпадении сигнатур или типов параметров метода). Т.е. этот метод по сути переопределять придётся. Но т.к. код базового метода меня устраивает, я не хочу его копировать - хочу переиспользовать.

#2 
alex445 патриот4 дня назад, 22:58
NEW 4 дня назад, 22:58 
в ответ alex445 4 дня назад, 22:54

Копировать код из метода в метод не хочется, т.к. если обновлять базовый метод, то и потомковый тоже придётся обновлять - но уже руками.

#3 
Срыв покровов патриот4 дня назад, 23:28
NEW 4 дня назад, 23:28 
в ответ alex445 4 дня назад, 22:54

приведи пример, как ты хочешь использовать эти свои классы.

#4 
AlexNek патриот3 дня назад, 13:07
AlexNek
NEW 3 дня назад, 13:07 
в ответ alex445 4 дня назад, 22:54
class Base<T>
{
    protected T CoreProcess(T input)
    {
        // общий код
    }
    public T ProcessValue(T input) => CoreProcess(input);
}

----

class Derived<T> : Base<T>
    where T : struct, INumber<T>
{
    public T? ProcessValue(T? input)
    {
        if (input is null)
            return null;

        return CoreProcess(input.Value);
    }
}
#5 
Программист коренной жительВчера, 11:02
NEW Вчера, 11:02 
в ответ alex445 4 дня назад, 22:46
Тогда можно будет переопределить поле Value как налловое и работать в нём с налловыми числами

Это ужасное решение. Никогда не используй new для переопределения.

Если я правильно понял, то ты хочешь что-то такое:

class Derived<T> : Base<T?>
where T: struct, INumber<T>
{
}


#6 
Программист коренной жительВчера, 11:16
NEW Вчера, 11:16 
в ответ alex445 4 дня назад, 22:54
Как сделать так, чтобы код не надо было повторять, заменяя лишь тип параметра?

Вариантов 2:

1) какпредложил AlexNek.Это если у тебя совсем простая логика. Т.е. если на вход null, то и вернуть null.

Если логика сложнее, то есть и другой вариант.

2) Разница между T и T? - проверка на null поэтому ее имеет смысл вынести:

class Bace<T> // нет ограничений на тип T
{
   protected virtual bool IsNull (T val)
   {
       return val is null;
   }

   public T ProcessValue(T input)
   {
       if (IsNull(input))
       {
          ....
       }
   }
}


class Derived<T> : Base<T?>
    where T : struct, INumber<T>
    {
        protected override bool IsNull(T? val)
        {
            return !val.HasValue;
        }
    }


#7 
alex445 патриотСегодня, 11:44
NEW Сегодня, 11:44 
в ответ AlexNek 3 дня назад, 13:07, Последний раз изменено Сегодня, 12:01 (alex445)

Новые данные - я не могу менять базовый класс. По крайней мере сейчас нет - надо с главным говорить, а он либо сам это будет делать, либо потом мне дадут, но не сейчас. И только если будет время - там на самом деле всё сложнее и на этот базовый класс завязано несколько других классов, а я сейчас просто вычленил конкретную проблему, обрав всё остальное.


Сейчас нужно найти какое-то более быстрое решение, без сильного переписывания базового класса. А на данный момент в базовом нет методов типа CoreProcess, как предложил Алекснек.


Если я правильно понял, то ты хочешь что-то такое:


class Derived<T> : Base<T?>
where T: struct, INumber<T>
{
}

Не это

Base


Вся суть

where T: struct

- чтобы можно было потом делать члены класса с типом T?


Хотел сделать универсальную обработку и для ссылочных типов, и для значений. Для этого в базовом классе есть обработка ProcessValue с типом T без ограничений на тип. Это то, что мне досталось. Теперь надо сделать тип, который таким же образом обрабатывает только числа. Я решил унаследоваться от базового класса и просто использовать его метод ProcessValue, но сделать дополнительное ограничение на тип, чтобы были возможны только числа:


where T: INumber<T>


Ну и потом добавилось требование - налловые числа:


where T: struct, INumber<T>


А сам код ProcessValue должен по сути остаться тем же - там весьма базовый функционал, который подходит под все типы. Но в таком наследовании его базовую версию нельзя вызвать из потомкового класса - см.


Если как-то поиграться с переопределениями и сигнатурами при наследовании нельзя, чтобы не переписывать базовый метод ProcessValue, то у нас есть пока костыль попроще и побыстрее, а переписывать базовый класс будем позже.


Забыл лишь написать, что ProcessValue может быть объявлен виртуальным в базовом классе - это можно сделать сейчас.

#8 
Программист коренной жительСегодня, 12:52
NEW Сегодня, 12:52 
в ответ alex445 Сегодня, 11:44

Пусть у тебя есть класс:

class Bace<T> // нет ограничений на тип T
{
   public T ProcessValue(T input) {...}
}

Делаешь так:

class Derived<T> : Base<T?>
where T: struct, INumber<T>
{
}


После этого:

var num = new Derived<int>();
var processedVal = num.ProcessValue(5);


тип у processedVal будет T?.


А в этом случае


var num1 = new Base<int>();
var processedVal1 = num1.ProcessValue(5);

тип у processedVal1 будет T.


Ну или попробуй объяснить, какой тебе нужен результат :)



#9 
alex445 патриотСегодня, 13:09
NEW Сегодня, 13:09 
в ответ Программист Сегодня, 12:52, Последний раз изменено Сегодня, 13:14 (alex445)

Вобщем, похоже, не получится сделать один универсальный параметр и передавать в него всё подряд, но при этом в одной из переписок (overriding) сделать ограничение на налловые числа - из-за фундаментальных ограничений https://stackoverflow.com/a/79156942/5015385


По-любому нужны два варианта метода, использущие тип значения и ссылочный тип в качестве параметра. Т.е. решать проблему одного и того же кода надо по-другому, а не простой перегрузкой одного метода. Может, вариант Алекснека подойдёт со специализированными методами, которые вызываются из одного универсального.

#10 
alex445 патриотСегодня, 13:15
NEW Сегодня, 13:15 
в ответ Программист Сегодня, 12:52

После этого:

var num = new Derived<int>();
var processedVal = num.ProcessValue(5);


тип у processedVal будет T?.

Это почему? Откуда Т? взялось?

#11