Создать общий метод, ограничивающий перечисление - PullRequest
1055 голосов
/ 17 сентября 2008

Я создаю функцию для расширения концепции Enum.Parse, которая

  • Позволяет проанализировать значение по умолчанию в случае, если значение Enum не найдено
  • Не чувствителен к регистру

Итак, я написал следующее:

public static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
{
    if (string.IsNullOrEmpty(value)) return defaultValue;
    foreach (T item in Enum.GetValues(typeof(T)))
    {
        if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
    }
    return defaultValue;
}

Я получаю сообщение об ошибке Ограничение не может быть специальным классом System.Enum.

Достаточно справедливо, но есть ли обходной путь, чтобы разрешить Generic Enum, или я собираюсь имитировать функцию Parse и передавать тип в качестве атрибута, что заставляет ваш код предъявлять ужасные требования к боксу.

РЕДАКТИРОВАТЬ Все предложения ниже были высоко оценены, спасибо.

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

public static class EnumUtils
{
    public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
        if (string.IsNullOrEmpty(value)) return defaultValue;

        foreach (T item in Enum.GetValues(typeof(T)))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

РЕДАКТИРОВАТЬ: (16 февраля 2015 г.) Жюльен Лебосквейн недавно опубликовал обобщенное принудительное решение с типобезопасным типом в MSIL или F # ниже, которое стоит посмотреть, и upvote. Я удалю эту правку, если раствор поднимется вверх по странице.

Ответы [ 20 ]

3 голосов
/ 22 марта 2018

Существующие ответы верны на C # <= 7.2. Однако существует запрос функции языка C # <a href="https://github.com/dotnet/csharplang/issues/104" rel="nofollow noreferrer"> (связанный с запросом функции corefx ), позволяющий выполнить следующее:

public class MyGeneric<TEnum> where TEnum : System.Enum
{ }

На момент написания статьи функция «В обсуждении» на собраниях по языковому развитию.

EDIT

Согласно информации nawfal , это вводится в C # 7.3 .

3 голосов
/ 07 июля 2009

Интересно, что это возможно в других языках (управляемый C ++, IL напрямую).

Цитировать:

... Оба ограничения фактически генерируют действительный IL и могут также использоваться C #, если написаны на другом языке (вы можете объявить эти ограничения в управляемом C ++ или в IL).

Кто знает

3 голосов
/ 25 марта 2014

Это мое мнение. В сочетании с ответами и MSDN

public static TEnum ParseToEnum<TEnum>(this string text) where TEnum : struct, IConvertible, IComparable, IFormattable
{
    if (string.IsNullOrEmpty(text) || !typeof(TEnum).IsEnum)
        throw new ArgumentException("TEnum must be an Enum type");

    try
    {
        var enumValue = (TEnum)Enum.Parse(typeof(TEnum), text.Trim(), true);
        return enumValue;
    }
    catch (Exception)
    {
        throw new ArgumentException(string.Format("{0} is not a member of the {1} enumeration.", text, typeof(TEnum).Name));
    }
}

Источник MSDN

1 голос
/ 24 июля 2017

Как указано в других ответах ранее; хотя это не может быть выражено в исходном коде, на самом деле это может быть сделано на уровне IL. @Christopher Currens answer показывает, как IL с этим справляется.

С Fody s Add-In ExtraConstraints.Fody есть очень простой способ, в комплекте с инструментами для сборки, добиться этого. Просто добавьте их пакеты nuget (Fody, ExtraConstraints.Fody) в ваш проект и добавьте ограничения следующим образом (Выдержка из Readme of ExtraConstraints):

public void MethodWithEnumConstraint<[EnumConstraint] T>() {...}

public void MethodWithTypeEnumConstraint<[EnumConstraint(typeof(ConsoleColor))] T>() {...}

и Fody добавит необходимый IL для присутствия ограничения. Также обратите внимание на дополнительную функцию ограничения делегатов:

public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
{...}

public void MethodWithTypeDelegateConstraint<[DelegateConstraint(typeof(Func<int>))] T> ()
{...}

Что касается Enums, вы также можете обратить внимание на весьма интересный Enums.NET .

1 голос
/ 01 ноября 2016

Я создал расширение Метод to get integer value from enum взгляните на реализацию метода

public static int ToInt<T>(this T soure) where T : IConvertible//enum
{
    if (typeof(T).IsEnum)
    {
        return (int) (IConvertible)soure;// the tricky part
    }
    //else
    //    throw new ArgumentException("T must be an enumerated type");
    return soure.ToInt32(CultureInfo.CurrentCulture);
}

это использование

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way
1 голос
/ 05 октября 2010

Мне всегда нравилось это (вы можете изменить соответствующим образом):

public static IEnumerable<TEnum> GetEnumValues()
{
  Type enumType = typeof(TEnum);

  if(!enumType.IsEnum)
    throw new ArgumentException("Type argument must be Enum type");

  Array enumValues = Enum.GetValues(enumType);
  return enumValues.Cast<TEnum>();
}
1 голос
/ 25 июля 2013

Я инкапсулировал решение Vivek в служебный класс, который вы можете использовать повторно. Обратите внимание, что вы все равно должны определить ограничения типа «где T: struct, IConvertible» для вашего типа.

using System;

internal static class EnumEnforcer
{
    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your type parameter {0} is an enum.",
                typeParameterName,
                methodName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    /// <param name="inputParameterName">Name of the input parameter of this page.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName, string inputParameterName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your input parameter {2} is of correct type.",
                typeParameterName,
                methodName,
                inputParameterName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="exceptionMessage">Message to show in case T is not an enum.</param>
    public static void EnforceIsEnum<T>(string exceptionMessage)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(exceptionMessage);
        }
    }
}
1 голос
/ 23 апреля 2012

Мне понравилось решение Christopher Currens, использующее IL, но для тех, кто не хочет заниматься сложным бизнесом, включающим MSIL в процесс сборки, я написал аналогичную функцию на C #.

Обратите внимание, что вы не можете использовать общие ограничения, такие как where T : Enum, потому что Enum является специальным типом. Поэтому я должен проверить, действительно ли данный универсальный тип является enum.

Моя функция:

public static T GetEnumFromString<T>(string strValue, T defaultValue)
{
    // Check if it realy enum at runtime 
    if (!typeof(T).IsEnum)
        throw new ArgumentException("Method GetEnumFromString can be used with enums only");

    if (!string.IsNullOrEmpty(strValue))
    {
        IEnumerator enumerator = Enum.GetValues(typeof(T)).GetEnumerator();
        while (enumerator.MoveNext())
        {
            T temp = (T)enumerator.Current;
            if (temp.ToString().ToLower().Equals(strValue.Trim().ToLower()))
                return temp;
        }
    }

    return defaultValue;
}
0 голосов
/ 30 мая 2018

Просто для полноты ниже приведено решение Java. Я уверен, что то же самое можно сделать и в C #. Это избавляет от необходимости указывать тип в любом месте кода - вместо этого вы указываете его в строках, которые вы пытаетесь проанализировать.

Проблема в том, что нет никакого способа узнать, какому перечислению может соответствовать строка - поэтому ответ состоит в том, чтобы решить эту проблему.

Вместо того, чтобы принимать только строковое значение, примите String, которая имеет и перечисление, и значение в форме "enumeration.value". Рабочий код ниже - требуется Java 1.8 или новее. Это также сделает XML более точным, так как вы увидите что-то вроде color = "Color.red" вместо просто color = "red".

Вы бы вызвали метод acceptEnumeratedValue () со строкой, содержащей имя точечного значения имени перечисления.

Метод возвращает формальное перечисляемое значение.

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;


public class EnumFromString {

    enum NumberEnum {One, Two, Three};
    enum LetterEnum {A, B, C};


    Map<String, Function<String, ? extends Enum>> enumsByName = new HashMap<>();

    public static void main(String[] args) {
        EnumFromString efs = new EnumFromString();

        System.out.print("\nFirst string is NumberEnum.Two - enum is " + efs.acceptEnumeratedValue("NumberEnum.Two").name());
        System.out.print("\nSecond string is LetterEnum.B - enum is " + efs.acceptEnumeratedValue("LetterEnum.B").name());

    }

    public EnumFromString() {
        enumsByName.put("NumberEnum", s -> {return NumberEnum.valueOf(s);});
        enumsByName.put("LetterEnum", s -> {return LetterEnum.valueOf(s);});
    }

    public Enum acceptEnumeratedValue(String enumDotValue) {

        int pos = enumDotValue.indexOf(".");

        String enumName = enumDotValue.substring(0, pos);
        String value = enumDotValue.substring(pos + 1);

        Enum enumeratedValue = enumsByName.get(enumName).apply(value);

        return enumeratedValue;
    }


}
0 голосов
/ 23 ноября 2017

Если после этого можно использовать прямое приведение, я думаю, вы можете использовать базовый класс System.Enum в своем методе, где это необходимо. Вам просто нужно аккуратно заменить параметры типа. Таким образом, реализация метода будет выглядеть так:

public static class EnumUtils
{
    public static Enum GetEnumFromString(string value, Enum defaultValue)
    {
        if (string.IsNullOrEmpty(value)) return defaultValue;
        foreach (Enum item in Enum.GetValues(defaultValue.GetType()))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

Тогда вы можете использовать его как:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...