Перечисление c # бесполезно.
Вы можете избежать приведения из вашего типа И ограничить значения, которые могут быть явным образом приведены к вашему типу, создав закрытый класс и предоставив операторы неявного / явного преобразования.
- Предоставьте неявный оператор для преобразования из вашего типа в общий тип int, чтобы вам не приходилось приводить.
- Предоставьте явный оператор для преобразования из int в ваш тип, который выдает ошибку, если целое число не удовлетворяет ограничению, например (int x) => (x> = 0 && x <= 2). </li>
При использовании этого метода создайте общий неизменный базовый класс, такой как ConstrainedNumber<T>
, у которого есть конструктор, который принимает значение T и делегирует ограничение: delegate bool NumberConstraint<T>(T value)
. Конструктор должен выполнить значение через делегат ограничения и выдать исключение, если оно не соответствует ограничению. Базовый класс должен также заботиться о неявной операции преобразования в T и должен обрабатывать равенство, перегружая object.Equals (object) и object.GetHashCode (), определяя операторы == и! = Для типа ConstrainedNumber<T>
и реализуя IEquatable<T>
и IEquatable<ConstrainedNumber<T>>
. Я также рекомендую определить конструктор копирования для базового класса и всех производных типов. Затем клонирование может быть чисто реализовано в базовом классе путем извлечения конструктора копирования с помощью отражения, но это совершенно необязательно. Вы можете сами разобраться в реализации ConstrainedNumber<T>
, если только я не разместил ее где-нибудь в stackoverflow.
Вы можете предоставить именованные статические значения только для чтения в производном 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;