2015-07-01 2 views
4

У меня есть объект, который может находиться в одном из разных состояний (StateA, StateB и StateC), и в каждом из них есть релевантные данные различных типов (TStateA, TStateB, TStateC). Enums in Rust represent this perfectly. Каков наилучший способ реализовать что-то подобное на C#?Каков наилучший способ реализации перечисления Rust в C#?

This question may appear similar, но перечисления в Rust и union in C значительно отличаются.

+0

У меня есть идея, которая включает в себя метод расширения и, возможно, отражение, звуки уже усложнены, поэтому я не думаю, что это квалифицируется как «лучший способ». Все еще интересно? – nilbot

+0

Я думаю, это было бы полезно, если бы вы представили пример того, чего вы хотите достичь, вместо того, чтобы направлять людей к документу у вас будет больше шансов получить ответ. Просто взглянув на документацию, я согласен с @tweellt. – Dzyann

+0

См. Http://stackoverflow.com/questions/3151702/discriminated-union-in-c-sharp – AlexFoxGill

ответ

-3

Никогда не делал ничего в ржавчине, но, глядя на документы, мне кажется, что вам нужно будет реализовать учебник C# class. Так как Rust перечисляет даже поддержку функций и реализаций различных типов.

Probabily a abstract class.

0

Это очень похоже на абстрактные типы данных в функциональных языках. В C# нет прямой поддержки, но вы можете использовать один абстрактный класс для типа данных плюс один закрытый класс для каждого конструктора данных.

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

Конечно, нет ничего запрещающего вам добавить StateZ : MyState класс позже, и компилятор не предупредит вас, что ваши функции не являются исчерпывающими.

+2

Однако это не позволяет изменить состояние объекта после его создания. Конечно, вы можете создать новый, но личность теряется. Альтернатива заключается в том, чтобы обернуть все это в класс, который просто хранит «MyState», хотя это становится многословным. – delnan

3

Вам нужен класс, чтобы представить свой Entity

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# не имеет хороший способ написания кода для этого еще, возможно Pattern Matching, что в настоящее время рассматривается для C# .next поможет ,

Я думаю, что вы должны пересмотреть свой дизайн, чтобы использовать объектные отношения и сдерживание, пытаясь взять дизайн, который работает в rust и силы его в C# не может быть лучшим вариантом.

+0

Понравилось предложение соответствия шаблону – tweellt

+0

Рассмотрите возможность использования ключевого слова 'is', если вы просто проверяете тип -' if (maybeStateA is StateA) ' – jocull

+1

@jocull, тогда приведение должно выполняться внутри if, следовательно немного медленнее. Я ожидаю, что в реальной жизни будет использоваться смесь «есть» и «как». Или, может быть, абстрактные методы в классе «Штаты», например «IsInSateA()» –

0

Просто от задней части головы, как быстрое внедрение ...

Я бы первым объявить тип Enum и определить Перечислим элементы обычно.

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

К сожалению, параметры атрибута должны быть константой числа, строковой константой, выражением типа или значением перечисления. Это уменьшает гибкость. – Mephy

+0

@Mephy да. Но это соответствует требованию, нет? Нам нужен только f (item) -> type, который является инъекцией. И я думаю, что каждый определенный тип может быть получен путем отражения имени типа (строки). Исправьте меня, если я ошибаюсь ... – nilbot

1

Это может быть сумасшедшим, но если вы жесткие около эмуляции Нержавеющего как перечисления в C#, вы можете сделать это с помощью некоторых дженериков. Бонус: вы сохраняете тип безопасности, а также получаете 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; 
    } 
} 
+0

Вы используете тот же 'TValue' для всех перечислений, и это не то, что вы ожидаете от переименования Руста. Из связанной документации в OP один конструктор не имеет значения, другой имеет 3 ints, третий имеет 2 ints, а последний имеет String в качестве своих значений. – Mephy

+0

Одна из приятных моментов в том, что вы можете сделать значение «динамическим», если захотите, или вы можете использовать любой настраиваемый класс или структуру как значение. Это не точное совпадение с Rust, но это может помочь вам шаг в правильном направлении :) – jocull

0

Я недавно искал ржавчину и думал о тех же вопросах. Реальная проблема заключается в отсутствии шаблона деконструкции 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 в этом е xample)

Мне кажется, что если компилятор C# должен был реализовать ржавчину без изменения CLR, тогда это будет своего рода код, который он будет генерировать.

Было бы достаточно легко создать .ttinclude, чтобы сгенерировать это.

Деконструкции не так хорошо, как матч Руста, но нет никакой альтернативы, которая является одновременно эффективным и идиотом доказательством (неэффективный способ использовать что-то вроде

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

Резюмируя мой бессвязным - Это может быть сделано, но это вряд ли будет стоить усилий.

Смежные вопросы