Ограничение значения перечисления в параметре метода - PullRequest
2 голосов
/ 21 апреля 2010
enum Fruit
{
    Banana,
    Orange,
    Strawberry
    ...
    ...
    // etc, very long enum
}


PeelFruit(Fruit.Orange);
PeelFruit(Fruit.Banana);
PeelFruit(Fruit.Strawberry); // huh? can't peel strawberries!

Извините за неудачный пример, но, надеюсь, вы поняли идею.Есть ли способ ограничить значения перечисления, которые PeelFruit примет?

Я бы мог проверить их в методе с помощью переключателя или чего-то еще, но было бы здорово, если бы был способ сделать этоа) немного компактнее, и б) вызовет ошибку времени компиляции, а не ошибку времени выполнения.

[Fruit = Orange,Bannana]
void PeelFruit(Fruit fruit) { ... }

Ответы [ 8 ]

7 голосов
/ 21 апреля 2010

Это невозможно с функциями базового языка (это возможно при кодовых контрактах , хотя проверка во время компиляции доступна только в расширенной версии ). На самом деле вы даже не можете ограничить свой ввод значениями, определенными в вашем enum! Метод, который принимает параметр Fruit, будет принимать любой int (или любой другой тип перечисления, если он не является целым числом), если вызывающая сторона сначала преобразует его в Fruit:

PeelFruit((Fruit)10000); // Not a Fruit? Not a problem!
3 голосов
/ 21 апреля 2010

Из вашего примера не ясно, что вы действительно пытаетесь сделать. Вы не можете иметь ограничений на перечисления, как вы предлагаете, потому что все значения перечисления эквивалентны и одного типа. Компилятор заботится только о типах, а не о значениях, поэтому он не может различать.

Одним из способов достижения того же результата, если enum не требуется по какой-то особой причине, является использование системы типов в ваших интересах. Вместо перечисления определите иерархию объектов. Сами объекты могут быть одиночными заполнителями, если хотите. Таким образом, вы можете использовать систему типов для этого. Как пример

public abstract class Fruit { protected PeelableFruit() { } };
public abstract class PeelableFruit : Fruit { protected PeelableFruit() { } };
public class Orange : PeelableFruit { public static readonly Instance = new Orange(); protected Orange() { } };
etc. etc.

void AcceptsAnyFruit(Fruit ....) { }
void AcceptsPeelableFruit(PeelableFruit ....) { }

Имеет смысл?

1 голос
/ 21 апреля 2010

Можно было бы использовать объекты для представления фруктов вместо использования перечислений. Это позволит вам проверять время компиляции.

Каждый фруктовый класс реализует интерфейсы, чтобы сказать, что они разрешают или не разрешают.

Вместо

void PeelFruit(Fruit fruit) { ... }

это будет

void PeelFruit(IPeelable fruit) { ... }
1 голос
/ 21 апреля 2010

Вы можете получить проверку компилятора, используя код контракта . Я считаю, что вы можете сделать что-то вроде этого:

Contract.Requires(fruit != Fruit.Strawberry) 
0 голосов
/ 21 апреля 2010

Если вам нужны проверки во время компиляции, то, боюсь, вам придется пойти с классовой иерархией.

Если вы согласитесь на проверки во время выполнения, то наиболее компактной формой будет функция, проверяющая значение.

void PeelFruit(Fruit fruit)
{
    if (!Peelable(fruit))
        throw new ArgumentException("Fruit is nonpeelable."); 
    ... 
} 

static bool Peelable(Fruit fruit) { ... }

Сделайте эту функцию общедоступной, если вы хотите, чтобы ваши абоненты могли проверить, что они не передают неправильное значение. Тогда им не придется обрабатывать исключение.

0 голосов
/ 21 апреля 2010

То, что вы предлагаете, - это Атрибут, который представляет собой объект времени выполнения, а не время компиляции. Там действительно нет способа сделать то, что вы предлагаете, но вы действительно должны спросить себя, правильно ли дизайн. Если вы контролируете перечисление, возможно, имеет смысл иметь два отдельных перечисления. Как PeelableFruit и NonPeelableFruit.

0 голосов
/ 21 апреля 2010

Есть 2 перечисления, одно для очищаемых фруктов и одно для не очищаемых фруктов? Если перечисление не относится ко всем случаям, тогда они, вероятно, в любом случае должны быть отдельными вещами.

это, вероятно, лучше решить, используя иерархию типов с экземплярами fruit, peelableFruit и т. Д., Что заставит компилятор выполнять проверку типов, что на самом деле не будет выполняться в Enum, как было указано.

Вы можете использовать интерфейсы Marker без функциональности, чтобы идентифицировать типы и разрешить их передачу в методы.

0 голосов
/ 21 апреля 2010

Ну, одним очень простым способом было бы присвоить числовые значения, поэтому в вашем примере

enum Fruit
{
    Banana = 1,
    Orange = 2,
    Strawberry = 4
    ...
    ...
    // etc, very long enum
}

Тогда отметьте

if ((int) thefruit > 2)
...
else
...
...