Estou construindo uma função para estender o Enum.Parse
conceito que
- Permite que um valor padrão seja analisado caso um valor de Enum não seja encontrado
- Não diferencia maiúsculas de minúsculas
Então eu escrevi o seguinte:
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;
}
Estou recebendo uma restrição de erro não pode ser de classe especial System.Enum
.
É justo, mas existe uma solução alternativa para permitir uma enumeração genérica ou terei de imitar a Parse
função e passar um tipo como um atributo, o que força o requisito de boxe feio ao seu código.
EDITAR Todas as sugestões abaixo foram muito apreciadas, obrigado.
Já decidi (deixei o loop para manter a distinção entre maiúsculas e minúsculas - estou usando isso ao analisar 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;
}
}
EDIT: (16 de fev de 2015) Julien Lebosquain postou recentemente uma solução genérica de segurança de tipo imposta pelo compilador no MSIL ou F # abaixo, que vale a pena dar uma olhada e ser votada. Removerei esta edição se a solução surgir na parte superior da página.
fonte
Respostas:
Como o
Enum
Type implementa aIConvertible
interface, uma melhor implementação deve ser algo como isto:Isso ainda permitirá a passagem de tipos de valor implementados
IConvertible
. As chances são raras embora.fonte
Este recurso é finalmente suportado no C # 7.3!
O seguinte snippet (das amostras dotnet ) demonstra como:
Certifique-se de definir sua versão do idioma no seu projeto C # para a versão 7.3.
Resposta original abaixo:
Estou atrasado para o jogo, mas aceitei como um desafio ver como isso poderia ser feito. Não é possível em C # (ou VB.NET, mas role para baixo para F #), mas é possível em MSIL. Eu escrevi essa coisinha ....
O que gera uma função que se pareceria com isso, se fosse C # válido:
Em seguida, com o seguinte código C #:
Infelizmente, isso significa ter essa parte do seu código escrita em MSIL em vez de C #, com o único benefício adicional de poder restringir esse método
System.Enum
. Também é meio chato, porque é compilado em uma montagem separada. No entanto, isso não significa que você deve implantá-lo dessa maneira.Removendo a linha
.assembly MyThing{}
e chamando o ilasm da seguinte maneira:você obtém um módulo de rede em vez de um assembly.
Infelizmente, o VS2010 (e anteriormente, obviamente) não oferece suporte à adição de referências de módulo de rede, o que significa que você teria que deixá-lo em dois assemblies separados quando estiver depurando. A única maneira de adicioná-los como parte do seu assembly seria executar o csc.exe você mesmo, usando o
/addmodule:{files}
argumento da linha de comando. Não seria muito doloroso em um script MSBuild. Obviamente, se você é corajoso ou estúpido, pode executar o CSC manualmente manualmente toda vez. E certamente fica mais complicado, pois vários conjuntos precisam acessar.Então, isso pode ser feito em .Net. Vale a pena o esforço extra? Bem, acho que vou deixar você decidir sobre isso.
Solução F # como alternativa
Crédito extra: Acontece que uma restrição genérica
enum
é possível em pelo menos uma outra linguagem .NET além de MSIL: F #.Essa é mais fácil de manter, pois é uma linguagem conhecida com suporte completo ao IDE do Visual Studio, mas você ainda precisa de um projeto separado em sua solução. No entanto, produz naturalmente uma IL consideravelmente diferente (o código é muito diferente) e depende da
FSharp.Core
biblioteca, que, como qualquer outra biblioteca externa, precisa se tornar parte da sua distribuição.Veja como você pode usá-lo (basicamente o mesmo que a solução MSIL) e para mostrar que ele falha corretamente em estruturas sinônimas:
fonte
There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.
T
aSystem.Enum
não seria capaz de fazer todas as coisasT
que as pessoas esperavam, os autores do C # acharam que eles também poderiam proibi-la completamente. Considero a decisão infeliz, uma vez que o C # simplesmente ignorou qualquer tratamento especial deSystem.Enum
restrições, seria possível escrever umHasAnyFlags<T>(this T it, T other)
método de extensão com ordens de magnitude mais rápidas do queEnum.HasFlag(Enum)
e que verificassem seus argumentos com tipo.C # ≥ 7,3
A partir do C # 7.3 (disponível no Visual Studio 2017 ≥ v15.7), este código agora é completamente válido:
C # ≤ 7,2
Você pode ter uma restrição de enum imposta pelo compilador real abusando da herança de restrição. O seguinte código especifica tanto um
class
e umstruct
constrangimentos ao mesmo tempo:Uso:
Nota: isso é especificamente indicado na especificação de idioma do C # 5.0:
fonte
EnumClassUtils<System.Enum>
é suficiente para restringir T a todoSystem.Enum
e qualquer tipo derivado.struct
onParse
restringe ainda mais a um tipo de enum real. Você precisa restringir aEnum
em algum momento. Para fazer isso, sua classe precisa ser aninhada. Veja gist.github.com/MrJul/7da12f5f2d6c69f03d79where TClass : class
restrição ganha aqui?enum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int }
enum AlsoNotAnInt : long { Well, Bummer }
Editar
A pergunta foi agora respondida de maneira soberba por Julien Lebosquain . Eu também gostaria de estender sua resposta com
ignoreCase
,defaultValue
e argumentos opcionais, enquanto a adição deTryParse
eParseOrDefault
.Exemplos de uso:
Velho
Minhas antigas melhorias na resposta de Vivek usando os comentários e os 'novos' desenvolvimentos:
TEnum
para maior clareza para os usuáriosTryParse
lidarignoreCase
com o parâmetro existente (introduzido no VS2010 / .Net 4)default
valor genérico (introduzido no VS2005 / .Net 2)defaultValue
eignoreCase
resultando em:
fonte
Você pode definir um construtor estático para a classe que verificará se o tipo T é uma enumeração e lançará uma exceção, se não for. Este é o método mencionado por Jeffery Richter em seu livro CLR via C #.
Em seguida, no método de análise, você pode apenas usar Enum.Parse (typeof (T), input, true) para converter de string em enum. O último parâmetro verdadeiro é para ignorar o caso da entrada.
fonte
Enum
T
quando o construtor foi executado. Embora isso seja muito melhor do que esperar por um construtor de instância.Também deve ser considerado que, uma vez que o lançamento do C # 7.3 usando restrições de Enum é suportado imediatamente, sem a necessidade de verificação e outras informações.
Então, seguindo em frente e dado que você alterou a versão do idioma do seu projeto para C # 7.3, o código a seguir funcionará perfeitamente:
Caso você não saiba como alterar a versão do idioma para C # 7.3, veja a seguinte captura de tela:
EDIT 1 - Versão necessária do Visual Studio e considerando ReSharper
Para o Visual Studio reconhecer a nova sintaxe, você precisa pelo menos da versão 15.7. Você pode encontrar isso também mencionado nas notas de versão da Microsoft, consulte Visual Studio 2017 15.7 Notas de versão . Obrigado a @MohamedElshawaf por apontar esta pergunta válida.
Observe também que, no meu caso, o ReSharper 2018.1 até o momento em que este EDIT foi escrito ainda não suporta C # 7.3. A ativação do ReSharper destaca a restrição de Enum como um erro dizendo que não é possível usar 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' como restrição de parâmetro de tipo . ReSharper sugere como uma solução rápida para remover a restrição 'Enum' do parâmetro parâmetro T do método
No entanto, se você desativar o ReSharper temporariamente em Ferramentas -> Opções -> ReSharper Ultimate -> Geral, verá que a sintaxe está perfeitamente correta, pois você usa o VS 15.7 ou superior e o C # 7.3 ou superior.
fonte
where T : struct, Enum
, para evitar passar aSystem.Enum
si próprio como parâmetro de tipo.struct, Enum
. Minha lógica é explicada na resposta e nos comentários aqui .Modifiquei a amostra por dimarzionista. Esta versão funcionará apenas com Enums e não permitirá que as estruturas passem.
fonte
Tentei melhorar um pouco o código:
fonte
defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo)
, embora não saiba qual é o tipo de enum, apenas que o objeto é um enum.IsDefined
irá arruinar a insensibilidade do caso. Ao contrárioParse
,IsDefined
não temignoreCase
argumento, e o MSDN diz que apenas corresponde ao caso exato .Eu tenho um requisito específico no qual exigi usar enum com texto associado ao valor de enum. Por exemplo, quando eu uso enum para especificar o tipo de erro, é necessário descrever os detalhes do erro.
fonte
Espero que isso seja útil:
fonte
return (TValue)Enum.Parse(typeof (TValue), value);
porreturn (TValue)Enum.Parse(typeof (TValue), value, true);
Curiosamente, aparentemente isso é possível em outros idiomas (C ++ gerenciado, diretamente IL).
Citar:
Quem sabe
fonte
Esta é a minha opinião. Combinado a partir das respostas e do MSDN
Origem do MSDN
fonte
TEnum
na verdade, é um tipo de Enum, mastext
é uma string vazia, você obtém oArgumentException
ditado "TEnum deve ser um tipo de Enum", mesmo que seja.As respostas existentes são verdadeiras em C # <= 7.2. No entanto, há uma solicitação de recurso de linguagem C # (vinculada a uma solicitação de recurso corefx ) para permitir o seguinte;
No momento da redação, o recurso é "Em discussão" nas Reuniões de Desenvolvimento de Idiomas.
EDITAR
De acordo com as informações de nawfal , isso está sendo introduzido no C # 7.3 .
fonte
Eu sempre gostei disso (você pode modificar conforme apropriado):
fonte
Eu amei a solução de Christopher Currens usando IL, mas para aqueles que não querem lidar com negócios complicados de incluir o MSIL em seu processo de criação, escrevi uma função semelhante em C #.
Observe que você não pode usar restrições genéricas,
where T : Enum
porque o Enum é do tipo especial. Portanto, eu tenho que verificar se determinado tipo genérico é realmente enum.Minha função é:
fonte
Encapsulei a solução da Vivek em uma classe de utilitário que você pode reutilizar. Observe que você ainda deve definir restrições de tipo "onde T: struct, IConvertible" em seu tipo.
fonte
Eu criei uma extensão Método
to get integer value from enum
dê uma olhada na implementação do métodoisso é uso
fonte
Como indicado em outras respostas anteriores; Embora isso não possa ser expresso no código-fonte, ele pode ser feito no nível IL. A resposta de Christopher Currens mostra como a IL faz isso.
Com Fody s Add-In ExtraConstraints.Fody há uma maneira muito simples, completo com build-ferramentas, para alcançar este objectivo. Basta adicionar os pacotes de nuget (
Fody
,ExtraConstraints.Fody
) ao seu projeto e adicionar as restrições da seguinte forma (trecho do Leia-me do ExtraConstraints):e Fody adicionará a IL necessária para a restrição estar presente. Observe também o recurso adicional de restringir delegados:
Em relação ao Enums, você também pode anotar o Enums.NET altamente interessante .
fonte
Esta é a minha implementação. Basicamente, você pode configurar qualquer atributo e ele funciona.
fonte
Se não há problema em usar a conversão direta posteriormente, acho que você pode usar a
System.Enum
classe base em seu método, sempre que necessário. Você só precisa substituir os parâmetros de tipo com cuidado. Portanto, a implementação do método seria como:Então você pode usá-lo como:
fonte
Enum.ToObject()
produziria um resultado mais flexível. Alem de que, você poderia fazer as comparações de strings sem maiúsculas e minúsculas que negam a necessidade de chamarToLower()
Apenas para completar, a seguir é uma solução Java. Estou certo de que o mesmo poderia ser feito em C # também. Isso evita precisar especificar o tipo em qualquer lugar do código - em vez disso, você o especifica nas strings que você está tentando analisar.
O problema é que não há como saber qual enumeração a String pode corresponder - portanto, a resposta é resolver esse problema.
Em vez de aceitar apenas o valor da string, aceite uma String que tenha a enumeração e o valor no formato "enumeration.value". O código de trabalho está abaixo - requer Java 1.8 ou posterior. Isso também tornaria o XML mais preciso, pois você verá algo como color = "Color.red" em vez de apenas color = "red".
Você chamaria o método acceptEnumeratedValue () com uma sequência que contenha o nome da enumeração nome do valor do ponto.
O método retorna o valor enumerado formal.
fonte