Possíveis armadilhas de usar esta taquigrafia (baseada no método de extensão)

Atualização C # 6

Em C # 6 ?. agora é um recurso de idioma :

 // C#1-5 propertyValue1 = myObject != null ? myObject.StringProperty : null; // C#6 propertyValue1 = myObject?.StringProperty; 

A questão abaixo ainda se aplica a versões mais antigas, mas ao desenvolver um novo aplicativo usando o novo ?. operador é muito melhor prática.

Pergunta original:

Eu regularmente quero acessar propriedades em objects possivelmente nulos:

 string propertyValue1 = null; if( myObject1 != null ) propertyValue1 = myObject1.StringProperty; int propertyValue2 = 0; if( myObject2 != null ) propertyValue2 = myObject2.IntProperty; 

E assim por diante…

Eu uso isso tantas vezes que eu tenho um trecho para isso.

Você pode encurtar isso até certo ponto com um inline se:

 propertyValue1 = myObject != null ? myObject.StringProperty : null; 

No entanto, isso é um pouco desajeitado, especialmente se estiver configurando muitas propriedades ou se mais de um nível puder ser nulo, por exemplo:

 propertyValue1 = myObject != null ? (myObject.ObjectProp != null ? myObject.ObjectProp.StringProperty) : null : null; 

O que eu realmente quero é ?? syntax de estilo, que funciona muito bem para tipos nulos diretamente:

 int? i = SomeFunctionWhichMightReturnNull(); propertyValue2 = i ?? 0; 

Então eu vim com o seguinte:

 public static TResult IfNotNull( this T input, Func action, TResult valueIfNull ) where T : class { if ( input != null ) return action( input ); else return valueIfNull; } //lets us have a null default if the type is nullable public static TResult IfNotNull( this T input, Func action ) where T : class where TResult : class { return input.IfNotNull( action, null ); } 

Isso me permite essa syntax:

 propertyValue1 = myObject1.IfNotNull( x => x.StringProperty ); propertyValue2 = myObject2.IfNotNull( x => x.IntProperty, 0); //or one with multiple levels propertyValue1 = myObject.IfNotNull( o => o.ObjectProp.IfNotNull( p => p.StringProperty ) ); 

Isso simplifica essas chamadas, mas não tenho certeza sobre a verificação desse tipo de método de extensão – ele torna o código um pouco mais fácil de ler, mas ao custo de estender o object. Isso apareceria em tudo, embora eu pudesse colocá-lo em um namespace especificamente referenciado.

Este exemplo é bastante simples, um pouco mais complexo seria comparar duas propriedades de objects anuláveis:

 if( ( obj1 == null && obj2 == null ) || ( obj1 != null && obj2 != null && obj1.Property == obj2.Property ) ) ... //becomes if( obj1.NullCompare( obj2, (x,y) => x.Property == y.Property ) ... 

Quais são as armadilhas de usar extensões dessa maneira? Outros codificadores podem ser confundidos? Isso é apenas abuso de extensões?


Eu acho que o que eu realmente quero aqui é uma extensão de compilador / linguagem:

 propertyValue1 = myObject != null ? myObject.StringProperty : null; //becomes propertyValue1 = myObject?StringProperty; 

Isso tornaria o caso complexo muito mais fácil:

 propertyValue1 = myObject != null ? (myObject.ObjectProp != null ? myObject.ObjectProp.StringProperty) : null //becomes propertyValue1 = myObject?ObjectProp?StringProperty; 

Isso funcionaria apenas para tipos de valor, mas você poderia retornar equivalentes anuláveis:

 int? propertyValue2 = myObject?ObjectProp?IntProperty; //or int propertyValue3 = myObject?ObjectProp?IntProperty ?? 0; 

Nós, de forma independente, criamos exatamente o mesmo nome de método de extensão e implementação: Método de extensão de propagação nula . Portanto, não achamos que seja confuso ou um abuso de methods de extensão.

Eu escreveria seu exemplo de “vários níveis” com encadeamento da seguinte maneira:

 propertyValue1 = myObject.IfNotNull(o => o.ObjectProp).IfNotNull(p => p.StringProperty); 

Há um bug agora fechado no Microsoft Connect que propunha “?”. como um novo operador C # que executaria essa propagação nula. Mads Torgersen (da equipe de idiomas do C #) explicou brevemente por que eles não irão implementá-lo.

Aqui está outra solução, para membros encadeados, incluindo methods de extensão:

 public static U PropagateNulls ( this T obj ,Expression> expr) { if (obj==null) return default(U); //uses a stack to reverse Member1(Member2(obj)) to obj.Member1.Member2 var members = new Stack(); bool searchingForMembers = true; Expression currentExpression = expr.Body; while (searchingForMembers) switch (currentExpression.NodeType) { case ExpressionType.Parameter: searchingForMembers = false; break; case ExpressionType.MemberAccess: { var ma= (MemberExpression) currentExpression; members.Push(ma.Member); currentExpression = ma.Expression; } break; case ExpressionType.Call: { var mc = (MethodCallExpression) currentExpression; members.Push(mc.Method); //only supports 1-arg static methods and 0-arg instance methods if ( (mc.Method.IsStatic && mc.Arguments.Count == 1) || (mc.Arguments.Count == 0)) { currentExpression = mc.Method.IsStatic ? mc.Arguments[0] : mc.Object; break; } throw new NotSupportedException(mc.Method+" is not supported"); } default: throw new NotSupportedException (currentExpression.GetType()+" not supported"); } object currValue = obj; while(members.Count > 0) { var m = members.Pop(); switch(m.MemberType) { case MemberTypes.Field: currValue = ((FieldInfo) m).GetValue(currValue); break; case MemberTypes.Method: var method = (MethodBase) m; currValue = method.IsStatic ? method.Invoke(null,new[]{currValue}) : method.Invoke(currValue,null); break; case MemberTypes.Property: var method = ((PropertyInfo) m).GetGetMethod(true); currValue = method.Invoke(currValue,null); break; } if (currValue==null) return default(U); } return (U) currValue; } 

Então você pode fazer isso onde qualquer um pode ser nulo, ou nenhum:

 foo.PropagateNulls(x => x.ExtensionMethod().Property.Field.Method()); 

Se você tiver que verificar com muita frequência se uma referência a um object é nula, pode ser que você esteja usando o padrão de object nulo . Nesse padrão, em vez de usar null para lidar com o caso em que você não tem um object, você implementa uma nova class com a mesma interface, mas com methods e propriedades que retornam valores padrão adequados.

Como é

 propertyValue1 = myObject.IfNotNull(o => o.ObjectProp.IfNotNull( p => p.StringProperty ) ); 

mais fácil de ler e escrever do que

 if(myObject != null && myObject.ObjectProp != null) propertyValue1 = myObject.ObjectProp.StringProperty; 

Jafar Husain publicou uma amostra do uso de trees de expressão para verificar nulos em uma cadeia, macros de tempo de execução em c # 3 .

Isso obviamente tem implicações de desempenho. Agora, se tivéssemos uma maneira de fazer isso em tempo de compilation.

Eu só tenho que dizer que eu amo esse truque!

Eu não tinha percebido que os methods de extensão não implicam uma verificação nula, mas faz todo sentido. Como James apontou, a chamada do método de extensão em si não é mais cara do que um método normal, no entanto, se você está fazendo uma tonelada disso, então faz sentido seguir o Padrão de Objeto Nulo, que ljorquera sugeriu. Ou para usar um object nulo e ?? juntos.

 class Class1 { public static readonly Class1 Empty = new Class1(); . . x = (obj1 ?? Class1.Empty).X; 

ele torna o código um pouco mais fácil de ler, mas ao custo de estender o object. Isso apareceria em tudo,

Note que você não está realmente estendendo nada (exceto teoricamente).

 propertyValue2 = myObject2.IfNotNull( x => x.IntProperty, 0); 

irá gerar o código IL exatamente como se estivesse escrito:

 ExtentionClass::IfNotNull(myObject2, x => x.IntProperty, 0); 

Não há “sobrecarga” adicionada aos objects para suportar isso.

Para o leitor que não conhece, parece que você está chamando um método em uma referência nula. Se você quiser isso, sugiro colocá-lo em uma class de utilitário em vez de usar um método de extensão:

 propertyValue1 = Util.IfNotNull(myObject1, x => x.StringProperty ); propertyValue2 = Util.IfNotNull(myObject2, x => x.IntProperty, 0); 

O “Util” grates, mas é IMO o menor mal syntax.

Além disso, se você desenvolver isso como parte de uma equipe, pergunte gentilmente o que os outros pensam e fazem. A consistência em uma base de código para padrões usados ​​com frequência é importante.

Embora os methods de extensão geralmente causem mal-entendidos quando chamados de instâncias nulas, acho que a intenção é bem direta neste caso.

 string x = null; int len = x.IfNotNull(y => y.Length, 0); 

Eu gostaria de ter certeza de que este método estático funciona em tipos de valor que podem ser nulos, como int?

Edit: compilador diz que nenhum destes são válidos:

  public void Test() { int? x = null; int a = x.IfNotNull(z => z.Value + 1, 3); int b = x.IfNotNull(z => z.Value + 1); } 

Fora isso, vá em frente.

Não é uma resposta para a pergunta exata, mas há Operador Nulo-Condicional no C # 6.0 . Eu posso argumentar que será uma má escolha usar a opção no OP desde C # 6.0 🙂

Então sua expressão é mais simples

 string propertyValue = myObject?.StringProperty; 

Caso myObject seja nulo, ele retorna null. Caso a propriedade seja um tipo de valor, você deve usar o tipo anulável equivalente, como

 int? propertyValue = myObject?.IntProperty; 

Ou, caso contrário, você pode se unir ao operador de coalescência nulo para fornecer um valor padrão no caso de nulo. Por exemplo,

 int propertyValue = myObject?.IntProperty ?? 0; 

?. não é a única syntax disponível. Para propriedades indexadas, você pode usar ?[..] . Por exemplo,

 string propertyValue = myObject?[index]; //returns null in case myObject is null 

Um comportamento surpreendente do ?. operador é que ele pode ignorar de forma inteligente as chamadas subseqüentes .Member se o object for nulo. Um exemplo é dado no link:

 var result = value?.Substring(0, Math.Min(value.Length, length)).PadRight(length); 

Nesse caso, o result é null se o value for nulo e value.Length expression não resultaria em NullReferenceException .

Pessoalmente, mesmo depois de toda a sua explicação, não me lembro como isso funciona:

 if( obj1.NullCompare( obj2, (x,y) => x.Property == y.Property ) 

Isso pode ser porque eu não tenho experiência em C #; no entanto, eu poderia ler e entender tudo o que há em seu código. Eu prefiro manter a linguagem de código agnóstica (especialmente para coisas triviais) para que amanhã, outro desenvolvedor possa alterá-la para uma linguagem totalmente nova sem muita informação sobre a linguagem existente.

Aqui está outra solução usando myObject.NullSafe (x => x.SomeProperty.NullSafe (x => x.SomeMethod)), explicado em http://www.epitka.blogspot.com/