Помощь с математическими операндами в классе (c #)

#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:

Причина, по которой ваш код не компилируется, объясняется ошибкой компилятора. Содержащий тип является общим определением типа, и общий тип, построенный на основе такого типа, не считается таким же типом.

У меня есть несколько вопросов:

  1. Почему Rational тип должен быть универсальным? Рациональное число определяется как число, которое может быть выражено как частное / дробь двух целых чисел (где знаменатель не является 0 ). Почему бы не сделать тип не универсальным и просто использовать int повсюду? Или вы намерены использовать этот тип для других целых типов, таких как long и BigInteger ? В таком случае рассмотрите возможность использования чего-то вроде предложения Алиостада, если вам нужен какой-то механизм совместного использования кода.
  2. Почему вы хотите, чтобы произведение двух рациональных чисел было равно сумме их числителей на сумму их знаменателей? Для меня это не имеет смысла.

В любом случае, вы, похоже, хотите иметь возможность «в общем виде» добавить два экземпляра «добавляемого» типа. К сожалению, в настоящее время в 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: полностью изменил мой ответ.