我有一个可以处于不同状态(StateA,StateB和StateC)之一的实体,并且每个实体都有不同类型的相关数据(TStateA,TStateB,TStateC)。 Enums in Rust represent this perfectly。在C#中实现类似这样的东西的最佳方法是什么?
This question may appear similar,但Rust中的枚举和C中的联合显着不同。
答案 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。