Подтвердить что ты не робот

Почему число номеров не имеет общего интерфейса?

Недавно я столкнулся с проблемой, что хотел, чтобы функция работала как по удвоениям, так и по целым числам и задавалась вопросом, почему нет общего интерфейса для всех типов номеров (содержащих арифметические операторы и сравнения).

Это сделало бы такие функции записи, как Math.Min (которые существуют в перегрузках gazillion).

Будет ли введение дополнительного интерфейса нарушением изменения?

Edit: Я думаю об использовании этого, например

public T Add<T>(T a, T b) where T: INumber
{
    return a+b;
}

или

public T Range<T>(T x, T min, T max) where T:INumber
{
    return Max(x, Min(x, max), min);
}
4b9b3361

Ответ 1

Если вы хотите сделать такую ​​ "общую" арифметику, ваш вариант на строго типизированном языке, таком как С#, весьма ограничен. Марк Гравелл описал проблему следующим образом:

.NET 2.0 представил генерики в мир .NET, который открыл двери для многих изящных решений существующих проблем. Общие ограничения могут использоваться для ограничения аргументов типа для известных интерфейсов и т.д., Чтобы обеспечить доступ к функциональности - или для простых тестов равенства/неравенства в синглонах Comparer<T>.Default и EqualityComparer<T>.Default реализуются IComparer<T> и IEqualityComparer<T> соответственно (что позволяет нам для сортировки элементов, например, без необходимости знать что-либо о "Т", о котором идет речь).

При этом все-таки есть большой пробел, когда дело касается операторов. Поскольку операторы объявлены как статические методы, не существует IMath<T> или аналогичного эквивалентного интерфейса, который реализует все числовые типы; и действительно, гибкость операторов сделает это очень трудно сделать значимым образом. Хуже того: многие операторы на примитивных типах даже не существуют как операторы; вместо этого существуют прямые IL-методы. Чтобы сделать ситуацию еще более сложной, Nullable < > требует концепции "снятых операторов", где внутренний "T" описывает операторы, применимые к нулевому типу, но это реализовано как языковая функция и не предоставляется (делая отражение еще более забавным).

Однако в С# 4.0 было введено ключевое слово dynamic, которое вы можете использовать для правильной перегрузки во время выполнения:

using System;

public class Program
{
    static dynamic Min(dynamic a, dynamic b)
    {
        return Math.Min(a, b);        
    }

    static void Main(string[] args)
    {
        int i = Min(3, 4);
        double d = Min(3.0, 4.0);
    }
}

Вы должны знать, что это устраняет безопасность типа, и вы можете получить исключения во время выполнения, если динамическая среда выполнения не может найти подходящую перегрузку для вызова, например. потому что вы смешиваете типы.

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

Обратите внимание, что если вы только после определенных операций, вы действительно можете использовать интерфейсы, которые уже реализованы встроенными типами. Например, универсальная функция Min типа типа может выглядеть так:

public static T Min<T>(params T[] values) where T : IComparable<T>
{
    T min = values[0];
    foreach (var item in values.Skip(1))
    {
        if (item.CompareTo(min) < 0)
            min = item;
    }
    return min;
}

Ответ 2

i.e Как сделать 2 + 2.35? возврат 4 или 4.35 или 4.349999? как интерфейс понимает, какой присвоенный результат? Вы можете написать свой метод расширения и использовать перегрузку, чтобы решить эту проблему, но если мы хотим иметь интерфейс для всех целей, как долго будет размер интерфейса и найти полезную функцию сложно, также интерфейс добавляет некоторые накладные расходы, а числа обычно являются основой поэтому нужен быстрый способ.

Я думаю, что в вашем случае лучше написать класс расширения:

public static class ExtensionNumbers
{
    public static T Range<T>(this T input, T min, T max) where T : class 
    {
        return input.Max(input.Min(max), min);
    }

    public static T Min<T>(this T input, params T[] param) where T : class
    {
        return null;
    }

    private static T Max<T>(this T input, params T[] number) where T : class 
    {
        return null;
    }      

}

Я использовал where T : class для компиляции

Ответ 3

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

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

interface IDoSomething
{
  void DoSomething();
}

struct MyStruct : IDoSomething
{
  public MyStruct(int age)
  {
    this.Age = age;
  }

  public int Age;

  pubblic void DoSomething()
  {
    Age++;
  }
}

public void DoSomething(IDoSomething something)
{
  something.DoSomething();
} 

Когда я передаю свой экземпляр моей структуры, его бокс (становится ссылочным типом), в который я выполняю свою операцию DoSomething, но мой исходный экземпляр моей структуры не изменится.

Ответ 4

Отправляясь с ответом Мэтью, обратите внимание на разницу между тремя вызовами.

void DoSomething(ref MyStruct something)
{
  something.DoSomething();
} 

static void Main(string[] args)
{
  var s = new MyStruct(10);
  var i = (IDoSomething)s;

  DoSomething(s); // will not modify s
  DoSomething(i); // will modify i
  DoSomething(ref s); // will modify s, but with no reassignment

}

Ответ 5

Это не так просто, как введение интерфейса, поскольку доступные операторы различны для каждого типа и не всегда однородны (например, DateTime + TimeSpan = > DateTime, DateTime - DateTime = > TimeSpan).

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

Собственно, для Min/Max, Comparer<T>.Default.Compare(x,y) делает почти все, на что вы надеетесь.

Для других операторов: в .NET 4.0 dynamic здесь очень полезно:

T x = ..., y = ...;
T sum = (dynamic)x + (dynamic)y;

но в противном случае MiscUtil " общая поддержка операторов через класс Operator, т.е.

T x = ..., y = ...;
T sum = Operator.Add(x, y); // actually Add<T>

Ответ 6

Проблема заключается в том, что в архитектуре того, как хранятся числа, разные типы номеров обрабатываются по-разному. Для начала ваша формулировка неверна, и интерфейсы не будут работать, но я думаю, что вы получаете то, что вы хотите, чтобы числовые символы были свободно напечатаны.

Просто для начала о том, почему вы не захотите это делать, рассмотрите типы Integer - сопоставление один к одному в диапазоне значений, которые они могут представлять, в то время как типы с плавающей точкой имеют компонент с первичным и экспоненциальным компонентом, поскольку есть младенческое многие числа с плавающей запятой. Разработчики языка должны были бы сделать некоторые очень фундаментальные и потенциально ошибки, вызывая предположения в дизайне языка.

Взгляните на статью о математике с плавающей запятой для получения дополнительной информации.

Ответ 7

Это главная характеристика "строго типизированного языка". Это то, что позволяет избежать миллиардов ошибок в минуту. Конечно, мы хотим, чтобы int полностью отличался как двойной.