繁体   English   中英

在C#中实现Rust枚举的最佳方法是什么?

[英]What is the best way to implement a Rust enum in C#?

我有一个实体可以处于不同的状态之一(StateA,StateB和StateC),并且每个实体都有不同类型的相关数据(TStateA,TStateB,TStateC)。 Rust中的枚举代表了这一点 在C#中实现这样的东西的最佳方法是什么?

这个问题可能看似相似 ,但Rust中的枚举和C中的联合会有很大不同。

您需要一个班级来代表您的实体

class Entity {States state;}

然后你需要一组类来表示你的状态。

abstract class States {
   // maybe something in common
}
class StateA : MyState {
   // StateA's data and methods
}
class StateB : MyState {
   // ...
}

然后你需要写代码

StateA maybeStateA = _state as StateA;
If (maybeStateA != null)
{
    - do something with the data in maybeStateA
}

C#没有为此编写代码的好方法 ,也许正在为C#.next考虑的模式匹配会有所帮助。

我认为你应该重新考虑你的设计来使用对象关系和遏制,试图采用一种rust的设计并强制它进入C#可能不是最好的选择。

这可能很疯狂,但是如果你很难在C#中模拟类似Rust的枚举,你可以用一些泛型来做。 额外奖励:您保持类型安全并且还可以获得Intellisense! 您会因各种价值类型而失去一点灵活性,但我认为安全性可能会给您带来不便。

enum Option
{
    Some,
    None
}

class RustyEnum<TType, TValue>
{
    public TType EnumType { get; set; }
    public TValue EnumValue { get; set; }
}

// This static class basically gives you type-inference when creating items. Sugar!
static class RustyEnum
{
    // Will leave the value as a null `object`. Not sure if this is actually useful.
    public static RustyEnum<TType, object> Create<TType>(TType e)
    {
        return new RustyEnum<TType, object>
        {
            EnumType = e,
            EnumValue = null
        };
    }

    // Will let you set the value also
    public static RustyEnum<TType, TValue> Create<TType, TValue>(TType e, TValue v)
    {
        return new RustyEnum<TType, TValue>
        {
            EnumType = e,
            EnumValue = v
        };
    }
}

void Main()
{
    var hasSome = RustyEnum.Create(Option.Some, 42);
    var hasNone = RustyEnum.Create(Option.None, 0);

    UseTheEnum(hasSome);
    UseTheEnum(hasNone);
}

void UseTheEnum(RustyEnum<Option, int> item)
{
    switch (item.EnumType)
    {
        case Option.Some:
            Debug.WriteLine("Wow, the value is {0}!", item.EnumValue);
            break;
        default:
            Debug.WriteLine("You know nuffin', Jon Snow!");
            break;
    }
}

这是另一个演示自定义引用类型使用的示例。

class MyComplexValue
{
    public int A { get; set; }
    public int B { get; set; }
    public int C { get; set; }

    public override string ToString()
    {
        return string.Format("A: {0}, B: {1}, C: {2}", A, B, C);
    }
}

void Main()
{
    var hasSome = RustyEnum.Create(Option.Some, new MyComplexValue { A = 1, B = 2, C = 3});
    var hasNone = RustyEnum.Create(Option.None, null as MyComplexValue);

    UseTheEnum(hasSome);
    UseTheEnum(hasNone);
}

void UseTheEnum(RustyEnum<Option, MyComplexValue> item)
{
    switch (item.EnumType)
    {
        case Option.Some:
            Debug.WriteLine("Wow, the value is {0}!", item.EnumValue);
            break;
        default:
            Debug.WriteLine("You know nuffin', Jon Snow!");
            break;
    }
}

这看起来很像函数式语言中的抽象数据类型。 在C#中没有直接的支持,但是你可以为数据类型使用一个抽象类,为每个数据构造函数使用一个密封类。

abstract class MyState {
   // maybe something in common
}
sealed class StateA : MyState {
   // StateA's data and methods
}
sealed class StateB : MyState {
   // ...
}

当然,没有什么禁止你以后添加StateZ : MyState类,并且编译器不会警告你,你的功能并不详尽。

就在我的脑后,快速实施......

我首先声明枚举类型并正常定义枚举项。

enum MyEnum{
    [MyType('MyCustomIntType')]
    Item1,
    [MyType('MyCustomOtherType')]
    Item2,
}

现在,我定义属性类型MyTypeAttribute一个叫做物业TypeString

接下来,我需要编写一个扩展方法来为每个枚举项提取Type(首先在string中,然后再反映为实际类型):

public static string GetMyType(this Enum eValue){
    var _nAttributes = eValue.GetType().GetField(eValue.ToString()).GetCustomAttributes(typeof (MyTypeAttribute), false);
    // handle other stuff if necessary
    return ((MyTypeAttribute) _nAttributes.First()).TypeString;
}

最后,使用反射得到真实的类型......


我认为这种方法的优点在以后的代码中很容易使用:

var item = MyEnum.SomeItem;
var itemType = GetType(item.GetMyType());

我最近一直在研究Rust,并一直在思考同样的问题。 真正的问题是没有Rust解构模式匹配,但如果你愿意使用拳击,那么类型本身就是啰嗦但相对简单:

// You need a new type with a lot of boilerplate for every
// Rust-like enum but they can all be implemented as a struct
// containing an enum discriminator and an object value.
// The struct is small and can be passed by value
public struct RustyEnum
{
    // discriminator type must be public so we can do a switch because there is no equivalent to Rust deconstructor
    public enum DiscriminatorType
    {
        // The 0 value doesn't have to be None 
        // but it must be something that has a reasonable default value 
        // because this  is a struct. 
        // If it has a struct type value then the access method 
        // must check for Value == null
        None=0,
        IVal,
        SVal,
        CVal,
    }

    // a discriminator for users to switch on
    public DiscriminatorType Discriminator {get;private set;}

    // Value is reference or box so no generics needed
    private object Value;

    // ctor is private so you can't create an invalid instance
    private RustyEnum(DiscriminatorType type, object value)
    {
        Discriminator = type;
        Value = value;
    }

    // union access methods one for each enum member with a value
    public int GetIVal() { return (int)Value; }
    public string GetSVal() { return (string)Value; }
    public C GetCVal() { return (C)Value; }

    // traditional enum members become static readonly instances
    public static readonly RustyEnum None = new RustyEnum(DiscriminatorType.None,null);

    // Rusty enum members that have values become static factory methods
    public static RustyEnum FromIVal(int i) 
    { 
        return  new RustyEnum(DiscriminatorType.IVal,i);
    }

    //....etc
}

用法是:

var x = RustyEnum::FromSVal("hello");
switch(x.Discriminator)
{
    case RustyEnum::DiscriminatorType::None:
    break;
    case RustyEnum::DiscriminatorType::SVal:
         string s = x.GetSVal();
    break;
    case RustyEnum::DiscriminatorType::IVal:
         int i = x.GetIVal();
    break;
}

如果你添加一些额外的公共const字段,这可以减少到

var x = RustyEnum::FromSVal("hello");
switch(x.Discriminator)
{
    case RustyEnum::None:
    break;
    case RustyEnum::SVal:
         string s = x.GetSVal();
    break;
    case RustyEnum::IVal:
         int i = x.GetIVal();
    break;
}

...但是您需要一个不同的名称来创建无价值的成员(在本例中为None)

在我看来,如果C#编译器要在不改变CLR的情况下实现生锈枚举,那么这就是它将生成的那种代码。

创建一个.ttinclude来生成它很容易。

解构并不像Rust匹配那么好,但是没有其他方法可以有效和愚蠢地证明(效率低的方法是使用像

x.IfSVal(sval=> {....})

总结一下我的漫无边际 - 它可以做到,但它不太可能值得付出努力。

从来没有在Rust做过任何事情,但是看看它接触到的文档,你必须实现一本教科书C# class 由于Rust枚举甚至支持各种类型的功能和实现。

Probabily是一个抽象类

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM