这个问题可能看起来相似, 但是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#可能不是最佳选择。
is
关键字 -- if (maybeStateA is StateA)
。 - jocullmaybeStateA != null
才是真正说明为什么它不适用于 C# 的原因。即使在 C# 中,枚举也不能为 null(这就是 Rust 特性如此出色的原因)。使用这种方法,你必须记住每次它可能为空,而在 Rust 中的 Option<T> 强制你考虑 None 状态。 - Gregor A. Lamche这可能有些疯狂,但如果你想在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;
}
}
TValue
,这并不符合 Rust 枚举的预期。根据 OP 中链接的文档,其中一个构造函数没有值,另一个有 3 个整数,第三个有 2 个整数,最后一个则将字符串作为其值。 - Mephypublic abstract record State(int Count); // An abstract base state that tracks shared information, here the total number of iterations.
public sealed record InitialState(int Count) : State(Count) { public InitialState() : this(0) {}}
public record StateA(int Count, string Token, int InnerCount) : State(Count) { }
public record StateB(int Count, string Token) : State(Count);
public sealed record FinalState(int Count) : State(Count);
public sealed record ErrorState(int Count) : State(Count);
string terminalString = "stop";
int maxIterations = 100;
State state = new InitialState();
// Negation pattern: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#logical-patterns
while (state is not FinalState && state is not ErrorState)
{
// Get the next token
string token = GetNextToken();
// Do some work with the current state + next token
Console.WriteLine("State = {0}", state);
// Transition to the new state
state = state switch // Switch Expression: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/switch-expression
{
var s when s.Count > maxIterations =>
new ErrorState(s.Count + 1),
InitialState s =>
new StateA(s.Count + 1, token, 0),
StateA s when s is { InnerCount : > 3 } => //https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#property-pattern
new StateB (s.Count + 1, token),
StateA s =>
s with { Count = s.Count + 1, Token = token, InnerCount = s.InnerCount + 1 }, // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record#nondestructive-mutation
StateB s when s.Token == terminalString =>
new FinalState(s.Count + 1),
StateB s =>
s with { Count = s.Count + 1, Token = token },
_ => throw new Exception($"Unknown state {state}"),
};
// Do some additional work with the new state
}
Console.WriteLine("State = {0}", state);
is
运算符和否定模式来确定迭代是否已经终止:while (state is not FinalState && state is not ErrorState)
var s when s.Count > maxIterations =>
StateA s when s is { InnerCount : > 3 } =>
s with { Count = s.Count + 1, Token = token, InnerCount = s.InnerCount + 1 },
when
子句的案例保护中使用。public interface IState { }
public sealed class State : IState
{
private string state;
private State(string state) => this.state = state;
// Enum-like states with no internal data
public static State Initial { get; } = new State(nameof(Initial));
public static State Final { get; } = new State(nameof(Final));
public static State Error { get; } = new State(nameof(Error));
public override string ToString() => state;
}
// Record states with internal data
public record class StateA(string Token, int Count) : IState;
public record class StateB(string Token) : IState;
string terminalString = "stop";
int maxIterations = 100;
(int count, IState state) = (0, State.Initial);
while (state != State.Final && state != State.Error)
{
// Get the next token
string token = GetNextToken();
// Do some work with the current state + next token
Console.WriteLine("State = {0}", state);
// Transition to the new state
state = state switch // Switch Expression: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/switch-expression
{
_ when count > maxIterations =>
state = State.Error,
State s when s == State.Initial =>
new StateA(token, 0),
StateA s when s is { Count : > 3 } => //https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#property-pattern
new StateB (token),
StateA s =>
s with { Token = token, Count = s.Count + 1 }, // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record#nondestructive-mutation
StateB s when s.Token == terminalString =>
State.Final,
StateB s =>
s with { Token = token },
_ => throw new Exception($"Unknown state {state}"),
};
// Do some additional work with the new state
}
Console.WriteLine("State = {0}", state);
使用静态单例来表示没有内部数据的状态,类似于Jimmy Bogard提出的枚举类概念,用于创建带有方法的枚举。
Case guards可以使用switch表达式之外的对象成员。在这里,迭代计数不包含在状态本身中,因此使用一个独立的计数器count
。
接口IState
没有成员,但你可以想象向其中添加一些内容,例如返回某个处理程序的方法:
public interface IState
{
public virtual Action<string> GetTokenHandler() => (s) => Console.WriteLine(s);
}
public record class StateA(string Token, int Count) : IState
{
public Action<string> GetTokenHandler() => (s) => Console.WriteLine("当前计数为 {0},当前令牌为 {1}", Count, Token);
}
该方法可以有一个虚拟默认实现,在某些状态下进行重写,而在其他状态下不进行重写。
有几个NuGet包可以定义类似的行为,例如:OneOf
让我通过一个简单的示例向您展示它是如何工作的。
(如果您对详细信息感兴趣,请查看这篇文章)。
public abstract class SucceededDiscountCalculation : DiscountCalculationResult
{
public double Percentage { get; }
protected SucceededDiscountCalculation(double percentage) => Percentage = percentage;
}
public abstract class FailedDiscountCalculation : DiscountCalculationResult
{
public Dictionary<string, object> ErrorData { get; }
protected FailedDiscountCalculation(params (string Key, object Value)[] errorData)
=> ErrorData = errorData.ToDictionary(item => item.Key, item => item.Value);
}
public abstract class DiscountCalculationResult
: OneOfBase<
DiscountCalculationResult.BirthdayDiscount,
DiscountCalculationResult.BirthdayIsNotSet,
DiscountCalculationResult.TotalFeeAbove10K,
DiscountCalculationResult.Newcomer,
DiscountCalculationResult.Under21,
DiscountCalculationResult.Above21>
{
public class BirthdayDiscount : SucceededDiscountCalculation
{
public BirthdayDiscount() : base(25) { }
}
public class BirthdayIsNotSet : FailedDiscountCalculation
{
public BirthdayIsNotSet(params (string Key, object Value)[] errorData) : base(errorData) { }
}
public class TotalFeeAbove10K : SucceededDiscountCalculation
{
public TotalFeeAbove10K() : base(15) { }
}
public class Newcomer : SucceededDiscountCalculation
{
public NewComer() : base(0) { }
}
public class Under21 : FailedDiscountCalculation
{
public Under21(params (string Key, object Value)[] errorData): base(errorData) { }
}
public class Above21 : SucceededDiscountCalculation
{
public Above21(): base(5) {}
}
}
OneOfBase
类继承的内容是重要的。如果一个方法返回一个DiscountCalculationResult
,那么你可以确定它是列出的类之一。OneOf提供了一个Switch
方法来一次处理所有情况。var result = engine.CalculateDiscount(dateOfBirth, orderTotal);
IActionResult actionResult = null;
result.Switch(
bDayDiscount => actionResult = Ok(bDayDiscount.Percentage),
bDayIsNotSet => {
_logger.Log(LogLevel.Information, "BirthDay was not set");
actionResult = StatusCode(StatusCodes.Status302Found, "Profile/Edit");
},
totalAbove10K => actionResult = Ok(totalAbove10K.Percentage),
totalAbove20K => actionResult = Ok(totalAbove20K.Percentage),
newcomer => actionResult = Ok(newcomer.Percentage),
under21 => {
_logger.Log(LogLevel.Information, $"Customer is under {under21.ErrorData.First().Value}");
actionResult = StatusCode(StatusCodes.Status403Forbidden);
},
above21 => actionResult = Ok(above21.Percentage)
);
最近我一直在研究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;
}
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#编译器要实现rust枚举而不改变CLR,那么它将生成这种类型的代码。
很容易创建一个.ttinclude来生成这个。
解构不像Rust match那样好,但没有既高效又白痴证明的替代方案(低效的方法是使用类似于
x.IfSVal(sval=> {....})
简单来说,你不能这么做。即使你觉得你能,也不要这么做,否则会自食其果。我们必须等待C#团队提出一种带有以下功能的类型。
我们期望的是一种多重结构体,具有不同的布局,但仍然适合于一个确定的内存堆栈。 Rust的处理方式是使用最大组的内存大小,例如
# Right now:
struct A { int a } # 4 bytes
struct B { int a, int b } # 8 bytes
# Can do but highly don't recommend would be waste of precious time, memory and cpu
struct AB {
A a,
B b
} # 12 bytes + 2 bytes to keep bool to check which struct should be used in code
# Future/Should be
super struct AB {
A(int),
B(int, int)
} # 8 bytes
MyEntity
的实体,它可以有StateA
、StateB
或StateC
。State
,并让StateA
、StateB
和StateC
实现这个抽象类。public abstract class State
{
public StateType Type { get; protected set; }
protected State(StateType type)
{
Type = type;
}
public abstract string DoSomething();
}
public class StateA : State
{
public string A { get; set; }
public StateA()
: base(StateType.A)
{
}
public override string DoSomething()
{
return $"A: {A}";
}
}
public class StateB : State
{
public double B { get; set; }
public StateB()
: base(StateType.B)
{
}
public override string DoSomething()
{
return $"B: {B}";
}
}
public class StateC : State
{
public DateTime C { get; set; }
public StateC()
: base(StateType.C)
{
}
public override string DoSomething()
{
return $"C: {C}";
}
}
public enum StateType
{
A = 1,
B = 2,
C = 3
}
string DoSomething()
方法将被实现,并且对于每个状态可能是不同的。State
类添加到你的MyEntity
中,并动态地改变状态。 public class MyEntity
{
public int Id { get; private set; }
public State State { get; private set; }
public MyEntity(int id, State state)
{
Id = id;
State = state;
}
public void SetState(State state)
{
State = state;
}
}
这看起来很像函数式语言中的抽象数据类型。虽然C#没有直接支持,但您可以使用一个抽象类作为数据类型,再加上每个数据构造函数一个密封类。
abstract class MyState {
// maybe something in common
}
sealed class StateA : MyState {
// StateA's data and methods
}
sealed class StateB : MyState {
// ...
}
StateZ : MyState
类,编译器不会警告你的函数不够全面。MyState
的类中,但这会变得冗长。 - user395760就我个人而言,作为一种快速实现的方法...
我会首先声明枚举类型并正常定义枚举项。
enum MyEnum{
[MyType('MyCustomIntType')]
Item1,
[MyType('MyCustomOtherType')]
Item2,
}
MyTypeAttribute
的属性类型,并带有一个名为TypeString
的属性。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());
// Define the base state interface
public interface IState { }
// Define the specific state interfaces
public interface IStateA : IState
{
// Define relevant methods and properties for StateA
}
public interface IStateB : IState
{
// Define relevant methods and properties for StateB
}
public interface IStateC : IState
{
// Define relevant methods and properties for StateC
}
// Define the concrete state classes
public class StateA : IStateA
{
// Define relevant data for StateA
public string DataA { get; set; }
}
public class StateB : IStateB
{
// Define relevant data for StateB
public int DataB { get; set; }
}
public class StateC : IStateC
{
// Define relevant data for StateC
public bool DataC { get; set; }
}
// Define your entity class
public class MyEntity
{
private IState currentState;
public void TransitionToStateA(string data)
{
currentState = new StateA { DataA = data };
}
public void TransitionToStateB(int data)
{
currentState = new StateB { DataB = data };
}
public void TransitionToStateC(bool data)
{
currentState = new StateC { DataC = data };
}
// Use pattern matching to work with specific states
public void PerformAction()
{
switch (currentState)
{
case StateA stateA:
// Perform actions specific to StateA
Console.WriteLine("Performing action for StateA");
Console.WriteLine(stateA.DataA);
break;
case StateB stateB:
// Perform actions specific to StateB
Console.WriteLine("Performing action for StateB");
Console.WriteLine(stateB.DataB);
break;
case StateC stateC:
// Perform actions specific to StateC
Console.WriteLine("Performing action for StateC");
Console.WriteLine(stateC.DataC);
break;
default:
throw new InvalidOperationException("Invalid state");
}
}
}