Existe uma maneira melhor de obter o nome da propriedade quando transmitida por meio de uma expressão lambda? Aqui está o que eu tenho atualmente.
por exemplo.
GetSortingInfo<User>(u => u.UserId);
Funcionou lançando-o como uma expressão membere apenas quando a propriedade era uma string. porque nem todas as propriedades são seqüências de caracteres, eu tive que usar o objeto, mas isso retornaria uma expressão unária para elas.
public static RouteValueDictionary GetInfo<T>(this HtmlHelper html,
Expression<Func<T, object>> action) where T : class
{
var expression = GetMemberInfo(action);
string name = expression.Member.Name;
return GetInfo(html, name);
}
private static MemberExpression GetMemberInfo(Expression method)
{
LambdaExpression lambda = method as LambdaExpression;
if (lambda == null)
throw new ArgumentNullException("method");
MemberExpression memberExpr = null;
if (lambda.Body.NodeType == ExpressionType.Convert)
{
memberExpr =
((UnaryExpression)lambda.Body).Operand as MemberExpression;
}
else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
{
memberExpr = lambda.Body as MemberExpression;
}
if (memberExpr == null)
throw new ArgumentException("method");
return memberExpr;
}
c#
linq
lambda
expression-trees
Schotime
fonte
fonte
MemberExpression
abordagem listada aqui apenas para obter o nome do membro, não para obter o nome realMemberInfo
propriamente dito, porqueMemberInfo
não é garantido que o retorno seja do tipo refletido em certos cenários "dervied: base". Consulte expressão lambda-não-retornando-esperado-memberinfo . Me tropeçou uma vez. A resposta aceita também sofre com isso.Respostas:
Recentemente, fiz uma coisa muito semelhante para tornar um método OnPropertyChanged seguro.
Aqui está um método que retornará o objeto PropertyInfo para a expressão. Ele lança uma exceção se a expressão não for uma propriedade.
O
source
parâmetro é usado para que o compilador possa fazer inferência de tipo na chamada do método. Você pode fazer o seguintefonte
u => u.OtherType.OtherTypesProperty
criaria um caso que a última instrução está verificando.if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))
permitir interfaces também.if(!propInfo.ReflectedType.IsAssignableFrom(type))
?Eu descobri outra maneira de fazer isso: digitar fortemente a fonte e a propriedade e inferir explicitamente a entrada para o lambda. Não tenho certeza se essa é a terminologia correta, mas aqui está o resultado.
E então chame assim.
e pronto, funciona.
Obrigado a todos.
fonte
GetInfo(nameof(u.UserId))
var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
Eu estava brincando com a mesma coisa e resolvi isso. Não foi totalmente testado, mas parece lidar com o problema com tipos de valor (o problema de não expressão que você encontrou)
fonte
o => o.Thing1.Thing2
voltariaThing2
, nãoThing1.Thing2
, o que é incorreto, se você está tentando usá-lo em EntityFramework incluiIsso lida com expressões de membros e unárias. A diferença é que você receberá um
UnaryExpression
se sua expressão representar um tipo de valor, enquanto você receberá umMemberExpression
se sua expressão representar um tipo de referência. Tudo pode ser convertido em um objeto, mas os tipos de valor devem ser encaixotados. É por isso que a UnaryExpression existe.Referência.Para facilitar a leitura (@Jowen), aqui está um equivalente expandido:
fonte
Com a correspondência de padrões C # 7:
Exemplo:
[Atualização] Correspondência de padrão C # 8:
fonte
agora em C # 6 você pode simplesmente usar nameof como este
nameof(User.UserId)
que tem muitos benefícios, entre eles está o de que isso é feito em tempo de compilação , não em tempo de execução.
https://msdn.microsoft.com/en-us/magazine/dn802602.aspx
fonte
Esta é uma implementação geral para obter o nome da sequência de campos / propriedades / indexadores / métodos / métodos de extensão / delegados de struct / class / interface / delegate / array. Eu testei com combinações de static / instance e variantes não genéricas / genéricas.
Isso também pode ser escrito em um
while
loop simples :Gosto da abordagem recursiva, embora a segunda possa ser mais fácil de ler. Pode-se chamar assim:
para imprimir o último membro.
Nota:
No caso de expressões encadeadas como
A.B.C
, "C" é retornado.Isso não funciona com
const
s, indexadores de matriz ouenum
s (impossível de cobrir todos os casos).fonte
Há um caso de ponta quando se trata de
Array
.Length. Enquanto 'Comprimento' é exposto como uma propriedade, você não pode usá-lo em nenhuma das soluções propostas anteriormente.Agora exemplo de uso:
Se
PropertyNameFromUnaryExpr
não fosse verificadoArrayLength
, "someArray" seria impresso no console (o compilador parece gerar acesso direto ao campo Comprimento de backup , como uma otimização, mesmo em Debug, portanto, no caso especial).fonte
Aqui está uma atualização do método proposto por Cameron . O primeiro parâmetro não é necessário.
Você pode fazer o seguinte:
Métodos de extensão:
Você pode:
fonte
u
como algum tipo, ele não pode fazer isso porque não há tipo para deduzir. O que você pode fazer éGetPropertyInfo<SomeType>(u => u.UserID)
Descobri que algumas das respostas sugeridas que detalham o
MemberExpression
/UnaryExpression
não captam subpropriedades aninhados /.ex)
o => o.Thing1.Thing2
retorna emThing1
vez deThing1.Thing2
.Essa distinção é importante se você estiver tentando trabalhar com o EntityFramework
DbSet.Include(...)
.Descobri que apenas analisar o
Expression.ToString()
parece funcionar bem e comparativamente rapidamente. Comparei-o com aUnaryExpression
versão, e mesmoToString
saindo daMember/UnaryExpression
para ver se isso era mais rápido, mas a diferença foi insignificante. Por favor, corrija-me se for uma péssima ideia.O método de extensão
(A verificação do delimitador pode até ser um exagero)
Demonstração (LinqPad)
Demonstração + código de comparação - https://gist.github.com/zaus/6992590
fonte
o => o.Thing1.Thing2
não retornaThing1
como você diz, masThing2
. De fato, sua resposta retorna algo como oThing1.Thing2
que pode ou não ser desejado.Thing1.Thing2
, nuncaThing1
. Eu disseThing2
significando o valor deo.Thing1.Thing2
, que é o ponto do predicado. Vou atualizar a resposta para refletir essa intenção.Thing1
? Eu não acho que isso refaça tudo.Estou usando um método de extensão para projetos anteriores ao C # 6 e o nome de () para aqueles direcionados ao C # 6.
E eu chamo assim:
Funciona bem com campos e propriedades.
fonte
Bem, não há necessidade de ligar
.Name.ToString()
, mas isso é amplamente, sim. A única consideração que você pode precisar é sex.Foo.Bar
deve retornar "Foo", "Bar" ou uma exceção - ou seja, você precisa iterar.(re-comentário) para mais informações sobre classificação flexível, veja aqui .
fonte
ToString
deve dar resultados feios para expressões unárias.Criei um método de extensão no ObjectStateEntry para poder sinalizar propriedades (das classes POCO do Entity Framework) como modificadas de maneira segura, pois o método padrão aceita apenas uma sequência. Aqui está minha maneira de obter o nome da propriedade:
fonte
Fiz a
INotifyPropertyChanged
implementação semelhante ao método abaixo. Aqui, as propriedades são armazenadas em um dicionário na classe base mostrada abaixo. Obviamente, nem sempre é desejável usar herança, mas para os modelos de exibição, acho que é aceitável e fornece referências de propriedades muito limpas nas classes de modelos de exibição.A classe base um pouco mais complexa é mostrada abaixo. Ele lida com a tradução da expressão lambda para o nome da propriedade. Observe que as propriedades são realmente pseudo-propriedades, pois apenas os nomes são usados. Mas parecerá transparente ao modelo de vista e referências às propriedades no modelo de vista.
fonte
public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }
. Pode ser mais lento, mas mais genérico e direto.Esta é outra resposta:
fonte
ModelMetadata
existe noSystem.Web.Mvc
espaço de nomes. Talvez ele não está apto para o caso geralDeixo esta função se você deseja obter múltiplos campos:
fonte
Aqui está outra maneira de obter o PropertyInfo com base nesta resposta. Isso elimina a necessidade de uma instância de objeto.
Pode ser chamado assim:
fonte
Atualizei a resposta de @ Cameron para incluir algumas verificações de segurança contra
Convert
expressões lambda digitadas:fonte
A partir do .NET 4.0, você pode usar
ExpressionVisitor
para encontrar propriedades:Aqui está como você usa este visitante:
fonte
Isso pode ser ideal
fonte
fonte