如何创建不兼容的数字子类型

时间:2019-01-08 03:23:29

标签: c#

注意:这个问题与this one几乎相同。但这是关于C#的,而不是Java的。

在Ada中,可以创建不兼容的等效数字类型:

type Integer_1 is range 1 .. 10;
type Integer_2 is range 1 .. 10;
A : Integer_1 := 8;
B : Integer_2 := A; -- illegal!

这可以防止意外的逻辑错误,例如向距离增加温度。

是否可以在C#中做类似的事情?例如

class BookId : int {}
class Book
{
    BookId Id;
}

class PageId : int {}
class Page
{
    PageId Id;
}

class Word
{
    BookId BookId;
    PageId PageId;
    string text;
}

var book = new Book { Id = 1 };
var page = new Page { Id = 1 };
var book = new Word
{
   BookId = book.Id,  // Ok
   PageId = book.Id,  // Illegal!
   string = "eratosthenes"
};

2 个答案:

答案 0 :(得分:1)

是的,您可以创建行为类似于数字值但不能相互分配的类型。您不能从数字类型派生,但是将一个数字包装到struct中将具有相当的效率(如果这是一个问题),或者您可以添加更多信息(例如单位)。如果不需要交叉类型的操作,甚至可以创建通用类型。

您可以看到Complex类型的完整操作和接口集,这些操作和接口使类型的行为与常规数字非常接近(包括根据需要来回转换)。

一些基础课:

class Distance
{
    float d;
    public Distance(float d)
    {
        this.d = d;
    }

    public static Distance operator+(Distance op1, Distance op2)
    {
        return new Distance(op1.d + op2.d);
    }

    // ==, !=, Equals and GetHashCode are not required but if you 
    // need one (i.e. for comparison you need ==, to use values of this 
    // type in Dictionaries you need GetHashCode)
    // you have to implement all 
    public static bool operator == (Distance op1, Distance op2)
    {
        return op1.d == op2.d;
    }
    public static bool operator !=(Distance op1, Distance op2)
    {
        return op1.d != op2.d;
    }

    public override bool Equals(object obj)
    {
        return (object)this == obj || ((obj is Distance) && (obj as Distance)==this);
    }
    public override int GetHashCode()
    {
        return d.GetHashCode();
    }

    // Some basic ToString so we can print it in Console/use in 
    // String.Format calls
    public override string ToString()
    {
        return $"{d} M";
    }
}

哪个可以添加相同类型的值,但不能添加任何其他类型:

Console.WriteLine(new Distance(1) + new Distance(2)); // "3 M"
// Console.WriteLine(new Distance(1) + 2); // fails to compile

在此类示例中,classstruct之间的选择主要是个人喜好,对于实际使用,请确保在选择一个值之前先了解值和参考类型之间的差异,然后确定对您有用的对象(结构可能更适合数字)。

更多信息: Units of measure in C# - almost-即使您未完全了解如何制作通用数字类型,也可以轻松地创建许多类型而无需太多代码(该帖子中的UnitDouble<T>),Arithmetic operator overloading for a generic class in C# -讨论了您想采用其他方法并支持各种基本数字类型(例如Distance<float>Distance<int>)时遇到的问题。

答案 1 :(得分:0)

事实证明,我的需求比我想象的要简单一些。我需要的是一个不能与另一个唯一ID混淆的唯一ID。最后,我使用了一个用于int的模板包装器。

class Id<T> {
  private int id;
  public Id(int id) { this.id = id; }
  public static implicit operator ID<T>(int value) { return new ID<T>(value); }
  public static implicit operator int(ID<T> value) { return value?.id ?? 0; }
  public static implicit operator int?(ID<T> value) { return value?.id; }  
  public static implicit operator ID<T>(int? value)
  {
    if (value == null) { return null; }
    return new ID<T>(value.Value);
  }
  public override string ToString() { return id.ToString(); }
}

class Book { Id<Book> Id; }
class Page { Id<Page> Id; }

Book.Id不能分配给Page.Id,但是两者都可以与int来回移动。

我现在意识到我已经在某个地方看到过这种模式,所以我想那不是原来的...