Переменная c # бесполезна.
Вы можете избежать кастинга из вашего типа И ограничить значения, которые могут быть явно переданы вашему типу, создав закрытый класс и предоставляя неявные / явные операторы преобразования.
Если используя этот метод, создайте универсальный неизменный базовый класс, такой как ConstrainedNumber<T>
, который имеет конструктор, который принимает значение T и делегирует для ограничения: delegate bool NumberConstraint<T>(T value)
. Конструктор должен запускать значение через делегат ограничения и вызывать исключение, если ему не удается выполнить ограничение. Базовый класс должен также заботиться о операции неявного преобразования в T и должен обрабатывать равенство, перегружая объект. Элементы (object) и object.GetHashCode (), определяющие операторы == и! = Для типа ConstrainedNumber<T>
и реализующие IEquatable<T>
и IEquatable<ConstrainedNumber<T>>
. Я также рекомендую определить конструктор копирования для базового класса и всех производных типов. Клонирование затем может быть реализовано чисто в базовом классе путем извлечения конструктора копирования посредством отражения, но это совершенно необязательно. Вы можете понять реализацию ConstrainedNumber<T>
самостоятельно, если я уже не разместил ее где-то в stackoverflow.
Вы можете указать значения static readonly в своем производном ConstrainedNumber, чтобы вы могли обращаться к ним так же, как enum.
public sealed class ReturnValue: ConstrainedNumber<int>
{
public static readonly NumberConstraint<int> constraint = (int x) => (x >= 0 && x < 3);
public static readonly ReturnValue Success = new ReturnValue(0);
public static readonly ReturnValue FailReason1 = new ReturnValue(1);
public static readonly ReturnValue FailReason2 = new ReturnValue(2);
private ReturnValue( int value ): base( value, constraint ) {}
private ReturnValue( ReturnValue original ): base (original) {} //may be used to support IClonable implementation in base class
public static explicit operator ReturnValue( int value )
{
switch(value) //switching to return an existing instance is more efficient than creating a new one and re-checking the constraint when there is a limited number of allowed values; if the constraint was more generic, such as an even number, then you would instead return a new instance here, and make your constructors public.
{
case 0: return Success;
case 1: return FailReason1;
case 2: return FailReason2;
}
throw new ArgumentException( "Value fails to meet the constraint defined for " + typeof(ReturnValue).FullName + ".", "value" );
}
}
Вы можете использовать эту технику для любого ограничения. Например, класс с названием EvenNumber может иметь ограничение, которое возвращает true, если заданное число четное. В этом случае вы просто сделаете свои конструкторы общедоступными и упростите свой оператор статического преобразования, чтобы просто вернуть новый EvenNumber, вместо того чтобы переключиться на возврат одного из ограниченных существующих экземпляров.
Его можно использовать как это:
EvenNumber x = (EvenNumber)2;
EvenNumber y = (EvenNumber)3; //throws exception "Value fails to meet the constraint defined for {namespace}.EvenNumber." A c# enum would stupidly allow such a cast, creating an invalid EvenNumber, breaking the object-oriented model
int z = x; //implicit conversion, no cast necessary;