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

时间:2015-07-01 15:46:22

标签: c# enums

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

This question may appear similar,但Rust中的枚举和C中的联合显着不同。

6 个答案:

答案 0 :(得分:3)

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

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的Pattern Matching会有所帮助。

我认为您应该重新考虑您的设计以使用对象关系和遏制,尝试采用适用于rust强制将其设计为C#的设计可能不是最佳选择。< / p>

答案 1 :(得分:1)

这可能很疯狂,但是如果你很难在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;
    }
}

答案 2 :(得分:0)

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

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

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

答案 3 :(得分:0)

从我的脑后,作为一个快速实施...

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

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

现在我使用名为MyTypeAttribute的属性定义属性类型TypeString

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

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());

答案 4 :(得分:0)

我最近一直在研究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=> {....})

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

答案 5 :(得分:-3)

在Rust中从来没有做过任何事情,但是看着它向我发送的文档,你必须实现一本教科书C#class。由于Rust枚举甚至支持各种类型的函数和实现。

Probabily abstract class