Существует ли ограничение, которое ограничивает мой общий метод для числовых типов?

Ваш псевдокод в значительной степени верен. Для этого примера предположим, что у нас был вызов метода foo.bar(), где foo: T. Я собираюсь использовать полнофункциональный синтаксис (FQS), чтобы быть однозначным в отношении того, с каким типом метода вызывается, например. A::bar(foo) или A::bar(&***foo). Я просто собираюсь написать кучу случайных заглавных букв, каждый из которых является лишь некоторым произвольным типом / признаком, за исключением того, что T всегда является типом исходной переменной foo, вызываемой методом.

Ядро алгоритма:

  • Для каждого «шаг разыменования» U (то есть установите U = T, а затем U = *T, ...), если существует метод bar, где тип приемника (тип self в методе) точно совпадает с U, используйте его ( a «по методу значений» ) в противном случае , добавьте один auto-ref (возьмите & или &mut приемника), и если приемник какого-либо метода совпадает с &U, используйте его ( «метод autorefd» )

Примечательно, что все рассматривает «тип приемника» метода , а не тип Self признака, т. е. impl ... for Foo { fn method(&self) {} } думает о &Foo при совпадении метод и fn method2(&mut self) будут думать о &mut Foo при сопоставлении.

Это ошибка, если на внутренних этапах есть допустимые методы с несколькими признаками (то есть может быть только нулевой или один признак меня то есть в каждом из 1. или 2., но может быть один действительный для каждого: первый из 1 будет принят первым), а присущие ему методы имеют приоритет над чертами. Это также ошибка, если мы дойдем до конца цикла, не найдя ничего, что соответствует. Это также ошибка для рекурсивных реализаций Deref, которые делают цикл бесконечным (они попадут в «предел рекурсии»).

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

Добавлена ​​только одна автозаправка, потому что

  • , если не было никакой привязки, все становится плохо / медленным, так как каждый тип может иметь произвольное количество ссылок, взятых
  • , взяв одну ссылку &foo, сохраняет сильное соединение с foo (он является адресом самого foo), но он принимает больше запусков, чтобы потерять его: &&foo - это адрес некоторой временной переменной в стеке, в которой хранятся &foo.

Примеры

Предположим, что у нас есть вызов foo.refm(), если foo имеет тип:

  • X, то мы начинаем с U = X, refm имеет тип приемника &..., так что шаг 1 не соответствует, принимая автореффиксы, дает нам &X, и это делает мат ch (с Self = X), поэтому вызов RefM::refm(&foo)
  • &X начинается с U = &X, который соответствует &self на первом шаге (с Self = X), и поэтому вызов RefM::refm(foo)
  • &&&&&X, это не соответствует ни шагу (черта не реализована для &&&&X или &&&&&X), поэтому мы разыскиваем один раз, чтобы получить U = &&&&X , который соответствует 1 (с Self = &&&X), а вызов - RefM::refm(*foo)
  • Z, не соответствует ни одному из шагов, поэтому разыменован один раз, чтобы получить Y, что также не означает, t, поэтому он разыменован снова, чтобы получить X, который не соответствует 1, но соответствует автореффиксу, поэтому вызов RefM::refm(&**foo).
  • &&A, 1. doesn 't, и не имеет значения 2. поскольку черта не реализована для &A (для 1) или &&A (для 2), поэтому она разыменовывается как &A, которая соответствует 1., с Self = A

Предположим, что мы имеем foo.m() и что A не Copy, если foo имеет тип:

  • A, то U = A напрямую соответствует self, поэтому вызов M::m(foo) с Self = A
  • &A, а затем 1. не совпадает, и оба не делают 2. (ни &A, ни &&A не реализуют этот признак), поэтому он разыменовывается как A, который соответствует, но M::m(*foo) требует принимать A по значению и, следовательно, перемещаться из foo, следовательно, ошибка .
  • &&A, 1. не соответствует, но автоопределение дает &&&A, что соответствует, поэтому вызов M::m(&foo) с Self = &&&A.

(Этот ответ основан на , код и достаточно близко к (слегка устаревшему) README . Нико Мацакис, главный автор этой части компилятора / языка, также взглянул на этот ответ.)

349
задан poke 15 December 2015 в 23:40
поделиться

10 ответов

C# не поддерживает это. Hejlsberg описал причины того, что не была реализована опция в интервью с Bruce Eckel :

И не ясно, что добавленная сложность стоит маленького урожая, который Вы получаете. Если что-то, что Вы хотите сделать, непосредственно не поддерживается в ограничительной системе, можно сделать это с шаблоном "фабрика". Вы могли иметь Matrix<T>, например, и в том Matrix требуется определить метод скалярного произведения. Это, конечно, которое означает Вас в конечном счете, должно понять, как умножиться два T с, но Вы не можете сказать, что как ограничение, по крайней мере, не, если T int, double, или float. Но то, что Вы могли сделать, имеют Ваш Matrix, берут в качестве аргумента Calculator<T>, и в [1 111], имеют метод, названный multiply. Вы идете реализация это, и Вы передаете ее Matrix.

Однако это приводит к довольно замысловатому коду, где пользователь должен предоставить их собственное Calculator<T> реализация для каждого T, что они хотят использовать. Пока он doesn’t должны быть расширяемы, т.е. если Вы просто хотите поддерживать постоянное число типов, такой как [1 116] и double, можно сойти с рук относительно простой интерфейс:

var mat = new Matrix<int>(w, h);

( Минимальная реализация в Сути GitHub. )

Однако, как только Вы хотите, чтобы пользователь был в состоянии предоставить их собственные, пользовательские типы, необходимо открыть эту реализацию так, чтобы пользователь мог предоставить их собственное Calculator экземпляры. Например, для инстанцирования матрицы, которая использует пользовательскую десятичную реализацию с плавающей точкой, DFP, you’d должны записать этот код:

var mat = new Matrix<DFP>(DfpCalculator.Instance, w, h);

†¦ и реализация все участники для [1 120].

альтернатива, которая, к сожалению, совместно использует те же ограничения, должна работать с занятиями по политике, , как обсуждено в ответе Sergey Shandar’s .

131
ответ дан Konrad Rudolph 23 November 2019 в 00:26
поделиться

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

я действительно задаюсь вопросом, почему это - проблема все же. Что Вы желаете сделать в своем классе IntegerFunction, который может только быть сделан к целым числам?

-5
ответ дан samjudson 4 November 2019 в 00:08
поделиться

Я думаю, что Вы неправильно понимаете дженерики. Если операция, которую Вы пытаетесь выполнить, только хороша для определенных типов данных тогда, Вы не делаете чего-то "универсального".

кроме того, так как Вы только желаете позволить функции работать над типами данных int тогда, Вам не должна быть нужна отдельная функция для каждого определенного размера. Просто взятие параметра в самом большом определенном типе позволит программу автоматически восходящему меньшие типы данных к нему. (т.е. передача Int16 автопреобразует в Int64 при вызове).

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

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

-9
ответ дан Tom Welch 23 November 2019 в 00:26
поделиться

Какой смысл осуществления?

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

static bool IntegerFunction(Int64 value) { }

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

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }
1
ответ дан dbkk 23 November 2019 в 00:26
поделиться

Вероятно, самое близкое, которое можно сделать,

static bool IntegerFunction<T>(T value) where T: struct

Не уверено, если Вы могли бы сделать следующий

static bool IntegerFunction<T>(T value) where T: struct, IComparable
, IFormattable, IConvertible, IComparable<T>, IEquatable<T>

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

13
ответ дан Haacked 23 November 2019 в 00:26
поделиться

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

я предполагаю, что единственное решение состоит в том, чтобы сделать проверку на этапе выполнения, которая, к сожалению, предотвращает проблему, взятую во время компиляции. Это пошло бы что-то like:-

static bool IntegerFunction<T>(T value) where T : struct {
  if (typeof(T) != typeof(Int16)  &&
      typeof(T) != typeof(Int32)  &&
      typeof(T) != typeof(Int64)  &&
      typeof(T) != typeof(UInt16) &&
      typeof(T) != typeof(UInt32) &&
      typeof(T) != typeof(UInt64)) {
    throw new ArgumentException(
      string.Format("Type '{0}' is not valid.", typeof(T).ToString()));
  }

  // Rest of code...
}

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

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

14
ответ дан ljs 23 November 2019 в 00:26
поделиться

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

я пошел бы далее и сказал бы, что нам нужно

static bool GenericFunction<T>(T value) 
    where T : operators( +, -, /, * )

, Или даже

static bool GenericFunction<T>(T value) 
    where T : Add, Subtract

, К сожалению, у Вас только есть интерфейсы, базовые классы и ключевые слова struct (должен быть тип значения), class (должен быть ссылочный тип), и new() (должен иметь конструктора по умолчанию)

, Вы могли обернуть число во что-то еще (подобный INullable<T>) как здесь на codeproject.

<час>

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

84
ответ дан Keith 23 November 2019 в 00:26
поделиться

Я задавался вопросом то же как samjudson, почему только к целым числам? и если это так, Вы могли бы хотеть создать класс помощника или что-то как этот для содержания всех типов, которые Вы хотите.

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

2
ответ дан Martin Marconcini 23 November 2019 в 00:26
поделиться

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

Какую версию .NET вы используете? Если вы используете .NET 3.5, то у меня есть реализация общих операторов в MiscUtil (бесплатно и т. Д.).

У меня есть такие методы, как T Add ( T x, T y) и другие варианты арифметики для разных типов (например, DateTime + TimeSpan ).

Кроме того, это работает для всех встроенных, расширенных и заказных операторов и кешей делегат для производительности.

Некоторые дополнительные сведения о том, почему это сложно, можно найти здесь .

Вы также можете узнать, что динамический (4.0) вид решает эту проблему тоже косвенно - т.е.

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect
15
ответ дан 23 November 2019 в 00:26
поделиться

Я бы использовал универсальный, который вы могли бы обрабатывать внешние ...

/// <summary>
/// Generic object copy of the same type
/// </summary>
/// <typeparam name="T">The type of object to copy</typeparam>
/// <param name="ObjectSource">The source object to copy</param>
public T CopyObject<T>(T ObjectSource)
{
    T NewObject = System.Activator.CreateInstance<T>();

    foreach (PropertyInfo p in ObjectSource.GetType().GetProperties())
        NewObject.GetType().GetProperty(p.Name).SetValue(NewObject, p.GetValue(ObjectSource, null), null);

    return NewObject;
}
1
ответ дан 23 November 2019 в 00:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: