首页 > 解决方案 > 如何在 C# 中创建范围和同义词常量?

问题描述

我刚开始在 C# 中工作,我想将一个新类型 ( Priority) 定义为一个整数,从 1 到 9。除此之外,我想创建三个新常量:

pri_Low,     which has value 1
pri_Default, which has value 5
pri_High,    which has value 9

我想做这样的事情:(C风格)

typedef TPriority = 1..9;
Const TPriority pri_Low     1;
Const TPriority pri_Default 5;
Const TPriority pri_High    9;

但是当我在互联网上寻找这个时,我得到的答案是“你需要创建一个类,你需要声明它static,并且......”。

我的第一反应是“别着急。我只想创建一个简单的数字范围并为其中三个赋予含义。没有类,没有构造函数,没有, ,staticpublic任何花哨的东西,只是简单的基础知识。”,是这甚至是可能的,还是 C# 进入“一切都是一个类”,甚至不允许如此简单的事情?privatefriend

标签: c#typedef

解决方案


你需要创建一个......不,你不需要做任何事情。

但是,您可以实现一个相对简单的类型。我说是相对的,因为乍一看它看起来像很多代码,但代码本身非常简单。

是的,来自具有 1..9 类型的 Object Pascal / Delphi,我真的很想念这些类型,但遗憾的是它们在 C# 或 .NET 中不存在。

在 C# 8 中,我们得到了“范围”,但它们不是类型,它们只是值。

现在,在简单和天真的一端,您可以使用枚举:

public enum Priority
{
    Low = 1,
    Default = 5,
    High = 9
}

但是,您现在没有 4 的优先级,如果您想让 .NET 告诉您一个值是否有效,您需要为所有有效值命名,因此您需要:

public enum Priority
{
    P1 = 1, P2, P3, P4, P5, P6, P7, P8, P9,
    
    Low = P1,
    Default = P5,
    High = P9
}

不幸的是,枚举不会阻止您存储无效值,这很好:

Priority p = (Priority)-5;

并且您未明确分配值的类型中的任何字段都将具有(Priority)0默认值,而不是 5。

所以...

如果您想要一种实际上不接受小于 1 或大于 9 的值的类型,则只能自己创建一个递归,因此这里有一个简单的 Priority 类型,适用于范围 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)}";
}

推荐阅读