Ваш псевдокод в значительной степени верен. Для этого примера предположим, что у нас был вызов метода 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 . Нико Мацакис, главный автор этой части компилятора / языка, также взглянул на этот ответ.)
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 .
Нет никакого единственного интерфейсного или базового класса, который они все наследовали (который также не наследован другими классами), таким образом, простой ответ нет.
я действительно задаюсь вопросом, почему это - проблема все же. Что Вы желаете сделать в своем классе IntegerFunction, который может только быть сделан к целым числам?
Я думаю, что Вы неправильно понимаете дженерики. Если операция, которую Вы пытаетесь выполнить, только хороша для определенных типов данных тогда, Вы не делаете чего-то "универсального".
кроме того, так как Вы только желаете позволить функции работать над типами данных int тогда, Вам не должна быть нужна отдельная функция для каждого определенного размера. Просто взятие параметра в самом большом определенном типе позволит программу автоматически восходящему меньшие типы данных к нему. (т.е. передача Int16 автопреобразует в Int64 при вызове).
, Если бы Вы выполняете различные операции на основе фактического размера интервала, передаваемого в функцию тогда, я думал бы, что необходимо серьезно пересмотреть даже попытку сделать то, что Вы делаете. Если необходимо одурачить язык, необходимо думать немного больше о том, что Вы пытаетесь выполнить, а не как сделать то, что Вы хотите.
Сбой всего остального, параметр текстового объекта мог использоваться, и затем необходимо будет проверить тип параметра и принять соответствующие меры или выдать исключение.
Какой смысл осуществления?
, Поскольку люди уже указали, у Вас могла быть неродовая функция, берущая самый большой объект, и компилятор автоматически преобразует меньший ints для Вас.
static bool IntegerFunction(Int64 value) { }
, Если Ваша функция находится на производительности критическом пути (очень вряд ли, IMO), Вы могли бы обеспечить перегрузки для всех необходимых функций.
static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }
Вероятно, самое близкое, которое можно сделать,
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>
Для чего-то настолько определенного, почему не только имеют перегрузки для каждого типа, список так короток, и это возможно имело бы меньше объема потребляемой памяти.
К сожалению, Вы только в состоянии определить структуру в где пункт в этом экземпляре. Кажется странным, что Вы не можете определить 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...
}
, Который немного ужасен, я знаю, но по крайней мере обеспечивает необходимые ограничения.
я также изучил бы возможные последствия производительности для этой реализации, возможно, там существует более быстрый путь.
Нет никакого ограничения для этого. Это - реальная проблема для любого желающего использовать дженерики для числовых вычислений.
я пошел бы далее и сказал бы, что нам нужно
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.
Вы могли ввести ограничение во времени выполнения (путем отражения для операторов или проверки на типы), но это действительно теряет преимущество наличия дженерика во-первых.
Я задавался вопросом то же как samjudson, почему только к целым числам? и если это так, Вы могли бы хотеть создать класс помощника или что-то как этот для содержания всех типов, которые Вы хотите.
, Если все Вы хотите, целые числа, не используйте дженерик, который не универсален; или еще лучше, отклонение любой другой тип путем проверки его типа.
Этот вопрос относится к часто задаваемым вопросам, поэтому я публикую его как вики (поскольку я уже публиковал похожие сообщения раньше, но это более старый); в любом случае ...
Какую версию .NET вы используете? Если вы используете .NET 3.5, то у меня есть реализация общих операторов в MiscUtil (бесплатно и т. Д.).
У меня есть такие методы, как T Add
и другие варианты арифметики для разных типов (например, DateTime + TimeSpan
).
Кроме того, это работает для всех встроенных, расширенных и заказных операторов и кешей делегат для производительности.
Некоторые дополнительные сведения о том, почему это сложно, можно найти здесь .
Вы также можете узнать, что динамический
(4.0) вид решает эту проблему тоже косвенно - т.е.
dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect
Я бы использовал универсальный, который вы могли бы обрабатывать внешние ...
/// <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;
}