#c# #class #operands
#c# #класс #операнды
Вопрос:
public class Racional<T>
{
private T nominator;
private T denominator;
public T Nominator
{
get { return nominator; }
set { nominator = value; }
}
public T Denominator
{
get { return denominator; }
set { denominator = value; }
}
public Racional(T nominator, T denominator)
{
this.nominator = nominator;
this.denominator = denominator;
}
public static Racional<int> operator *(Racional<int> a, Racional<int> b)
{
return ((int)(a.nominator b.nominator, a.denominator b.denominator));
}
public override string ToString()
{
return "(" this.nominator " " this.denominator ")";
}
}
Меня интересует эта часть :
public static Racional<int> operator *(Racional<int> a, Racional<int> b)
{
return ((int)(a.nominator b.nominator, a.denominator b.denominator));
}
Что не так:
Одним из параметров двоичного оператора должен быть содержащий тип
Как я могу нормально закодировать эту часть для математических операций?
Комментарии:
1. Кстати, это домашнее задание или вы собираетесь использовать его в производственном коде? Ознакомьтесь msdn.microsoft.com/en-us/library /…
2. Возможно, вы захотите изменить имя класса на Rational…
3. Почему вы создаете класс, который обрабатывается фреймворком «из коробки»? Если это домашнее задание, то это ужасный способ сделать это.
4. @Roy: Я предполагаю, что это на языке, отличном от английского. По крайней мере, он опубликовал свой фактический код, а не рисковал появлением нерелевантных типографских ошибок.
5. Подождите минутку, вы хотите сказать, что 1/2 * 2/3 = 3/5? Этот код не только не компилируется, он не имеет никакого смысла. И вы говорите, что существуют рациональные числа, которые имеют произвольные типы в качестве числителя и знаменателя? Почему это вообще универсально ? Рациональное определяется как отношение двух целых чисел. У вас могут быть целые числа, строки, исключения и так далее. Этот код не имеет для меня никакого смысла. Можете ли вы объяснить?
Ответ №1:
Причина, по которой ваш код не компилируется, объясняется ошибкой компилятора. Содержащий тип является общим определением типа, и общий тип, построенный на основе такого типа, не считается таким же типом.
У меня есть несколько вопросов:
- Почему
Rational
тип должен быть универсальным? Рациональное число определяется как число, которое может быть выражено как частное / дробь двух целых чисел (где знаменатель не является0
). Почему бы не сделать тип не универсальным и просто использоватьint
повсюду? Или вы намерены использовать этот тип для других целых типов, таких какlong
иBigInteger
? В таком случае рассмотрите возможность использования чего-то вроде предложения Алиостада, если вам нужен какой-то механизм совместного использования кода. - Почему вы хотите, чтобы произведение двух рациональных чисел было равно сумме их числителей на сумму их знаменателей? Для меня это не имеет смысла.
В любом случае, вы, похоже, хотите иметь возможность «в общем виде» добавить два экземпляра «добавляемого» типа. К сожалению, в настоящее время в C # нет никакого способа выразить ограничение «имеет подходящий оператор сложения».
Метод № 1: Одним из обходных путей для этого в C # 4 является использование dynamic
типа для придания вам желаемой семантики «виртуального оператора».
public static Racional<T> operator *(Racional<T> a, Racional<T> b)
{
var nominatorSum = (dynamic)a.Nominator b.Nominator;
var denominatorSum = (dynamic)a.Denominator b.Denominator;
return new Racional<T>(nominatorSum, denominatorSum);
}
Оператор выдаст ошибку, если в типе нет подходящего оператора сложения.
Метод № 2: Другим (более эффективным) способом является использование деревьев выражений.
Сначала создайте и кэшируйте делегат, который может выполнять сложение путем компиляции соответствующего выражения:
private readonly static Func<T, T, T> Adder;
static Racional()
{
var firstOperand = Expression.Parameter(typeof(T), "x");
var secondOperand = Expression.Parameter(typeof(T), "y");
var body = Expression.Add(firstOperand, secondOperand);
Adder = Expression.Lambda<Func<T, T, T>>
(body, firstOperand, secondOperand).Compile();
}
(Статический конструктор выдаст ошибку, если у типа нет подходящего оператора сложения.)
Затем используйте это в операторе:
public static Racional<T> operator *(Racional<T> a, Racional<T> b)
{
var nominatorSum = Adder(a.Nominator, b.Nominator);
var denominatorSum = Adder(a.Denominator, b.Denominator);
return new Racional<T>(nominatorSum, denominatorSum);
}
Комментарии:
1. 1, очень интересно, что это возможно общим способом. Впервые я получил представление о том, для чего хороши деревья выражений.
2. Хорошо, почему не int во всем коде: потому что у меня есть такая лабораторная работа для моего урока программирования
Ответ №2:
Проблема здесь в том, что вы определяете оператор для Racional<int>
в классе Racional<T>
. Это невозможно. Типы не совпадают, вы можете определить operator только для Racional<T>
.
Обобщения не могут выражать обобщение операторов, поскольку они определены только для определенных типов. Решение заключается в создании класса и наследовании от Racional<int>
:
public class IntRacional : Racional<int>
{
public static Racional<int> operator (IntRacional a, IntRacional b)
{
return new Racional<int>()
{
Nominator = a.Nominator b.Nominator,
Denominator = a.Denominator b.Denominator
};
}
}
Комментарии:
1. Это не имеет смысла. Не могли бы вы пояснить?
2. Racional<T> и Racional<int> — это два разных класса в глазах компилятора. И вы не можете переопределить поведение одного класса в другом (исключая методы расширения).
Ответ №3:
Чтобы решить вашу проблему, вам необходимо предоставить функции преобразования из T
в некоторый тип, где operator
определен, и наоборот. Предполагая, что Int64
в большинстве случаев достаточно велико, это можно сделать следующим образом:
public class Racional<T>
{
private T nominator;
private T denominator;
static Converter<T,Int64> T_to_Int64;
static Converter<Int64,T> Int64_to_T;
public static void InitConverters(Converter<T,Int64> t2int, Converter<Int64,T> int2t )
{
T_to_Int64 = t2int;
Int64_to_T = int2t;
}
public T Nominator
{
get { return nominator; }
set { nominator = value; }
}
public T Denominator
{
get { return denominator; }
set { denominator = value; }
}
public Racional(T nominator, T denominator)
{
this.nominator = nominator;
this.denominator = denominator;
}
public static Racional<T> operator *(Racional<T> a, Racional<T> b)
{
return new Racional<T>(
Int64_to_T(T_to_Int64(a.nominator) T_to_Int64(b.nominator)),
Int64_to_T(T_to_Int64(a.denominator) T_to_Int64(b.denominator)));
}
// By the way, should this not be * instead of ???
//
// public static Racional<T> operator *(Racional<T> a, Racional<T> b)
// {
// return new Racional<T>(
// Int64_to_T(T_to_Int64(a.nominator) * T_to_Int64(b.nominator)),
// Int64_to_T(T_to_Int64(a.denominator) * T_to_Int64(b.denominator)));
// }
public override string ToString()
{
return "(" this.nominator " " this.denominator ")";
}
}
Конечно, это имеет тот недостаток, что вы должны обеспечить инициализацию этих преобразователей где-то при запуске программы, должно выглядеть следующим образом:
Racional<int>.InitConverters(x => (Int64)x, y => (int)y);
В реальной программе вы можете знать, какие возможные замены для T
вы собираетесь использовать. Таким образом, можно предоставить эти 3 или 4 вызова в статическом конструкторе, подобном этому:
public static Racional()
{
Racional<int>.InitConverters(x => (Int64)x, y => (int)y);
Racional<short>.InitConverters(x => (Int64)x, y => (short)y);
Racional<Int64>.InitConverters(x => (Int64)x, y => (Int64)y);
}
должно быть достаточно в большинстве случаев. Обратите внимание, что эта инициализация преобразователя повторяется для всех 3 типов еще 3 раза, повторно инициализируя функции преобразования несколько раз. На практике это не должно вызвать никаких проблем.
Комментарии:
1. виртуальный -1. Неверное предположение!! Не компилируется с ошибкой
One of the parameters of a binary operator must be the containing type
2. @Aliostad: полностью изменил мой ответ.