Вопрос по c# – Включить Enum (с атрибутом Flags), не объявляя все возможные комбинации?

43

Как включить перечисление, для которого установлен атрибут flags (или, точнее, используется для битовых операций)?

Я хочу быть в состоянии поразить все случаи в выключателе, который соответствует заявленным значениям.

Проблема в том, что если у меня есть следующее перечисление

<code>[Flags()]public enum CheckType
{
    Form = 1,   
    QueryString = 2,
    TempData = 4,
}
</code>

и я хочу использовать такой переключатель

<code>switch(theCheckType)
{
   case CheckType.Form:
       DoSomething(/*Some type of collection is passed */);
       break;

   case CheckType.QueryString:
       DoSomethingElse(/*Some other type of collection is passed */);
       break;

   case CheckType.TempData
       DoWhatever(/*Some different type of collection is passed */);
       break;
}
</code>

Если & quot; theCheckType & quot; установлен в оба CheckType.Form | CheckType.TempData Я хочу, чтобы он попадал в оба случая. Очевидно, что из-за разрыва он не будет попадать в оба примера, но кроме этого он также завершится неудачей, поскольку CheckType.Form не равен CheckType.Form | CheckType.TempData

Тогда единственное решение, как я вижу, состоит в том, чтобы обосновать каждую возможную комбинацию значений enum?

Что-то вроде

<code>    case CheckType.Form | CheckType.TempData:
        DoSomething(/*Some type of collection is passed */);
        DoWhatever(/*Some different type of collection is passed */);
        break;

    case CheckType.Form | CheckType.TempData | CheckType.QueryString:
        DoSomething(/*Some type of collection is passed */);
        DoSomethingElse(/*Some other type of collection is passed */);
        break;

... and so on...
</code>

Но это на самом деле не очень желательно (так как оно быстро станет очень большим)

Прямо сейчас у меня есть 3 Если условия сразу после друг друга

Что-то вроде

<code>if ((_CheckType & CheckType.Form) != 0)
{
    DoSomething(/*Some type of collection is passed */);
}

if ((_CheckType & CheckType.TempData) != 0)
{
    DoWhatever(/*Some type of collection is passed */);
}

....
</code>

Но это также означает, что если у меня есть перечисление с 20 значениями, оно должно проходить через 20 условий If каждый раз вместо «прыжка». только к необходимому «случаю» / /, как при использовании переключателя.

Есть какое-то волшебное решение, чтобы решить эту проблему?

Я думал о возможности циклически проходить через объявленные значения и затем использовать переключатель, тогда он будет нажимать на переключатель только для каждого объявленного значения, но я не знаю, как это будет работать, и если это отрицательное влияние на производительность - хорошая идея ( по сравнению с большим количеством "если"?

Есть ли простой способ перебрать все объявленные значения перечисления?

Я могу только придумать, как использовать ToString () и разделить на & quot;, & quot; а затем перебрать массив и проанализировать каждую строку.

ОБНОВИТЬ:

Я вижу, что я недостаточно хорошо объяснил объяснение. Мой пример прост (попытался упростить мой сценарий).

Я использую его для ActionMethodSelectorAttribute в Asp.net MVC, чтобы определить, должен ли метод быть доступен при разрешении URL / маршрута.

Я делаю это, заявляя что-то вроде этого в методе

<code>[ActionSelectorKeyCondition(CheckType.Form | CheckType.TempData, "SomeKey")]
public ActionResult Index()
{
    return View();
} 
</code>

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

Методы, которые он будет вызывать (doSomething (), doSomethingElse () и doWhwhat () в моем предыдущем примере), на самом деле будут иметь значение bool в качестве возвращаемого значения и будут вызываться с параметром (разные коллекции, у которых нет общего интерфейса, который может быть используется - см. мой пример кода в ссылке ниже и т. д.).

Надеюсь, чтобы дать лучшее представление о том, что я делаю, я вставил простой пример того, что я на самом деле делаю на pastebin - это можно найти здесьhttp://pastebin.com/m478cc2b8

Ваш Ответ

8   ответов
43

Как насчет этого. Конечно, аргументы и возвращаемые типы DoSomething и т. Д. Могут быть любыми.

class Program
{
    [Flags]
    public enum CheckType
    {
        Form = 1,
        QueryString = 2,
        TempData = 4,
    }

    private static bool DoSomething(IEnumerable cln)
    {
        Console.WriteLine("DoSomething");
        return true;
    }

    private static bool DoSomethingElse(IEnumerable cln)
    {
        Console.WriteLine("DoSomethingElse");
        return true;
    }

    private static bool DoWhatever(IEnumerable cln)
    {
        Console.WriteLine("DoWhatever");
        return true;
    }

    static void Main(string[] args)
    {
        var theCheckType = CheckType.QueryString | CheckType.TempData;
        var checkTypeValues = Enum.GetValues(typeof(CheckType));
        foreach (CheckType value in checkTypeValues)
        {
            if ((theCheckType & value) == value)
            {
                switch (value)
                {
                    case CheckType.Form:
                        DoSomething(null);
                        break;
                    case CheckType.QueryString:
                        DoSomethingElse(null);
                        break;
                    case CheckType.TempData:
                        DoWhatever(null);
                        break;
                }
            }
        }
    }
}
Error: User Rate Limit Exceeded MartinF
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded MartinF
Error: User Rate Limit Exceededstackoverflow.com/questions/1060760/…
13

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

EDIT: We could certainly make this code more compact through the use of LINQ and some helper functions, but I think it's easier to understand in the less sophisticated form. This may be case where maintainability trumps elegance.

Вот пример:

[Flags()]public enum CheckType
{
  Form = 1,       
  QueryString = 2,
  TempData = 4,
}

void PerformActions( CheckType c )
{
  // array of bits set in the parameter {c}
  bool[] actionMask = { false, false, false };
  // array of delegates to the corresponding actions we can invoke...
  Action availableActions = { DoSomething, DoSomethingElse, DoAnotherThing };

  // disassemble the flags into a array of booleans
  for( int i = 0; i < actionMask.Length; i++ )
    actionMask[i] = (c & (1 << i)) != 0;

  // for each set flag, dispatch the corresponding action method
  for( int actionIndex = 0; actionIndex < actionMask.Length; actionIndex++ )
  {
      if( actionMask[actionIndex])
          availableActions[actionIndex](); // invoke the corresponding action
  }
}

Альтернативно, если порядок, в котором вы оцениваете, не имеет значения, здесь есть более простое, более ясное решение, которое работает так же хорошо. Если порядок имеет значение, замените операции сдвига битов массивом, содержащим флаги в том порядке, в котором вы хотите их оценить:

int flagMask = 1 << 31; // start with high-order bit...
while( flagMask != 0 )   // loop terminates once all flags have been compared
{
  // switch on only a single bit...
  switch( theCheckType & flagMask )
  {
   case CheckType.Form:
     DoSomething(/*Some type of collection is passed */);
     break;

   case CheckType.QueryString:
     DoSomethingElse(/*Some other type of collection is passed */);
     break;

   case CheckType.TempData
     DoWhatever(/*Some different type of collection is passed */);
     break;
  }

  flagMask >>= 1;  // bit-shift the flag value one bit to the right
}
Error: User Rate Limit ExceededNoneError: User Rate Limit Exceeded0Error: User Rate Limit Exceeded0Error: User Rate Limit ExceededNone = 0Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded MartinF
1

На основании ваших правок и вашего реального кода я, вероятно, обновлюIsValidForRequest способ выглядеть примерно так:

public sealed override bool IsValidForRequest
    (ControllerContext cc, MethodInfo mi)
{
    _ControllerContext = cc;

    var map = new Dictionary<CheckType, Func<bool>>
        {
            { CheckType.Form, () => CheckForm(cc.HttpContext.Request.Form) },
            { CheckType.Parameter,
                () => CheckParameter(cc.HttpContext.Request.Params) },
            { CheckType.TempData, () => CheckTempData(cc.Controller.TempData) },
            { CheckType.RouteData, () => CheckRouteData(cc.RouteData.Values) }
        };

    foreach (var item in map)
    {
        if ((item.Key & _CheckType) == item.Key)
        {
            if (item.Value())
            {
                return true;
            }
        }
    }
    return false;
}
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded MartinF
-1

Самый простой способ - просто выполнитьORed enum, в вашем случае вы можете сделать следующее:

[Flags()]public enum CheckType
{
    Form = 1,   
    QueryString = 2,
    TempData = 4,
    FormQueryString = Form | QueryString,
    QueryStringTempData = QueryString | TempData,
    All = FormQueryString | TempData
}

Когда у вас естьenum настроить его теперь легко выполнитьswitch заявление.

Например, если я установил следующее:

var chkType = CheckType.Form | CheckType.QueryString;

Я могу использовать следующееswitch утверждение следующее:

switch(chkType){
 case CheckType.Form:
   // Have Form
 break;
 case CheckType.QueryString:
   // Have QueryString
 break;
 case CheckType.TempData:
  // Have TempData
 break;
 case CheckType.FormQueryString:
  // Have both Form and QueryString
 break;
 case CheckType.QueryStringTempData:
  // Have both QueryString and TempData
 break;
 case CheckType.All:
  // All bit options are set
 break;
}

Намного чище, и вам не нужно использоватьif заявление сHasFlag, Вы можете создавать любые комбинации, которые захотите, а затем легко читать оператор switch.

Я бы порекомендовал разбить вашenumsпопробуйте посмотреть, не смешиваете ли вы разные вещи в одно и то жеenum, Вы можете настроить несколькоenums уменьшить количество дел.

Error: User Rate Limit Exceeded
5

HasFlag

if(theCheckType.HasFlag(CheckType.Form)) DoSomething(...);
if(theCheckType.HasFlag(CheckType.QueryString)) DoSomethingElse(...);
if(theCheckType.HasFlag(CheckType.TempData)) DoWhatever(...);
4

Как насчетDictionary<CheckType,Action> что вы будете заполнять как

dict.Add(CheckType.Form, DoSomething);
dict.Add(CheckType.TempDate, DoSomethingElse);
...

разложение вашей ценности

flags = Enum.GetValues(typeof(CheckType)).Where(e => (value & (CheckType)e) == (CheckType)e).Cast<CheckType>();

а потом

foreach (var flag in flags)
{
   if (dict.ContainsKey(flag)) dict[flag]();
}

(код не проверен)

Error: User Rate Limit Exceeded MartinF
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
3

С C # 7 теперь вы можете написать что-то вроде этого:

public void Run(CheckType checkType)
{
    switch (checkType)
    {
        case var type when CheckType.Form == (type & CheckType.Form):
            DoSomething(/*Some type of collection is passed */);
            break;

        case var type when CheckType.QueryString == (type & CheckType.QueryString):
            DoSomethingElse(/*Some other type of collection is passed */);
            break;

        case var type when CheckType.TempData == (type & CheckType.TempData):
            DoWhatever(/*Some different type of collection is passed */);
            break;
    }
}
Error: User Rate Limit ExceededCheckType.Form | CheckType.QueryStringError: User Rate Limit ExceededswitchError: User Rate Limit ExceededQueryStringError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
0

switch (t1)
    {
        case var t when t.HasFlag(TST.M1):
            {
                break;
            }
        case var t when t.HasFlag(TST.M2):
            {
                break;
            }

Похожие вопросы