I've just started working in C#, and I would like to define a new type (Priority
) as an integer, going from 1 to 9. In top of this, I would like to create three new constants:
pri_Low, which has value 1
pri_Default, which has value 5
pri_High, which has value 9
I though of doing something like this: (C-style)
typedef TPriority = 1..9;
Const TPriority pri_Low 1;
Const TPriority pri_Default 5;
Const TPriority pri_High 9;
But when I look for this on the internet, I get answers like "You need to create a class, and you need to declare it static
, and ...".
My first reaction is "Hold your horses. I just want to create a simple range of numbers and give a meaning to three of them. No classes, no constructors, no static
, public
, private
, friend
or whatever fancy things, just simple basics.", is this even possible or is C# that into "Everything is a class" that such simple things are not even allowed?
You need to create a ... No, you don't need to do anything.
You can, however, implement a relatively simple type. I say relatively because on first glance it will look like a lot of code, but the code itself is really simple.
And yes, coming from Object Pascal / Delphi, which has the 1..9 types, I really miss those types, but sadly they don't exist in C# or .NET.
In C# 8, we got "ranges", but they're not types, they're just values.
Now, on the simple and naive end you can use an enum:
public enum Priority
{
Low = 1,
Default = 5,
High = 9
}
However, you now don't have a priority for 4, and if you want to ask .NET to tell you if a value is valid or not, you need to have names for all valid values, so you would need:
public enum Priority
{
P1 = 1, P2, P3, P4, P5, P6, P7, P8, P9,
Low = P1,
Default = P5,
High = P9
}
Unfortunately, enums doesn't prevent you from storing invalid values, this is fine:
Priority p = (Priority)-5;
and any fields in a type that you don't explicitly assign a value will have the value (Priority)0
as default, not 5.
So...
If you want a type that actually doesn't accept values less than 1 or higher than 9, you have no recurse than to create one yourself, so here's a simple Priority type for the range 1..9:
public struct Priority : IEquatable<Priority>, IEquatable<int>,
IComparable<Priority>, IComparable<int>, IFormattable
{
private const int _lowPriority = 1;
private const int _defaultPriority = 5;
private const int _highPriority = 9;
private readonly int _value;
public Priority(int value)
{
if (value < _lowPriority || value > _highPriority)
throw new ArgumentOutOfRangeException(nameof(value),
$"value must be in the range {_lowPriority}..{_highPriority}");
_value = value - _defaultPriority;
}
// the trick with `+/- _defaultPriority` is to make sure
// new Priority() is the same as new Priority(5)
public int Value => _value + _defaultPriority;
public static Priority Parse(string s)
=> Parse(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
public static Priority Parse(string s, NumberStyles style,
IFormatProvider provider)
{
if (TryParse(s, style, provider, out var priority))
return priority;
throw new FormatException($"Unable to parse priority '{s}'");
}
public static bool TryParse(string s, out Priority result)
=> TryParse(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo,
out result);
public static bool TryParse(string s, NumberStyles style,
IFormatProvider provider, out Priority result)
{
result = default;
if (s is null)
return false;
var span = s.AsSpan();
if (span.Length == 0 || span[0] != 'P')
return false;
span = span[1..];
if (!int.TryParse(span, style, provider, out int value))
return false;
if (value < _lowPriority || value > _highPriority)
return false;
result = new Priority(value);
return true;
}
public static readonly Priority Low = new Priority(_lowPriority);
public static readonly Priority Default = new Priority(_defaultPriority);
public static readonly Priority High = new Priority(_highPriority);
public static implicit operator int(Priority priority) => priority.Value;
public static explicit operator Priority(int value) => new Priority(value);
public static bool operator ==(Priority a, int b) => a.Value == b;
public static bool operator !=(Priority a, int b) => a.Value != b;
public static bool operator <(Priority a, int b) => a.Value < b;
public static bool operator >(Priority a, int b) => a.Value > b;
public static bool operator <=(Priority a, int b) => a.Value <= b;
public static bool operator >=(Priority a, int b) => a.Value >= b;
public static bool operator ==(int a, Priority b) => a == b.Value;
public static bool operator !=(int a, Priority b) => a != b.Value;
public static bool operator <(int a, Priority b) => a < b.Value;
public static bool operator >(int a, Priority b) => a > b.Value;
public static bool operator <=(int a, Priority b) => a <= b.Value;
public static bool operator >=(int a, Priority b) => a >= b.Value;
public bool Equals(Priority other) => Value == other.Value;
public bool Equals(int other) => Value == other;
public override bool Equals(object obj) => obj is Priority other
&& Equals(other);
public override int GetHashCode() => _value.GetHashCode();
public int CompareTo(Priority other) => Value.CompareTo(other.Value);
public int CompareTo(int other) => Value.CompareTo(other);
public override string ToString() => $"P{Value}";
public string ToString(string format, IFormatProvider formatProvider)
=> $"P{Value.ToString(format, formatProvider)}";
}