15

C#6アップデート

C#6?.では言語機能になりました

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

// C#6
propertyValue1 = myObject?.StringProperty;

以下の質問は古いバージョンにも当てはまりますが、新しい?.演算子を使用して新しいアプリケーションを開発する場合は、はるかに優れた方法です。

元の質問:

nullの可能性のあるオブジェクトのプロパティに定期的にアクセスしたい:

string propertyValue1 = null;
if( myObject1 != null )
    propertyValue1 = myObject1.StringProperty;

int propertyValue2 = 0;
if( myObject2 != null )
    propertyValue2 = myObject2.IntProperty;

等々...

私はこれを頻繁に使用するので、スニペットがあります。

次の場合は、インラインでこれをある程度短縮できます。

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

ただし、これは少し不格好です。特に、多くのプロパティを設定する場合や、複数のレベルをnullにできる場合は、次のようになります。

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

私が本当に必要としているのは??スタイル構文です。これは直接null型に最適です。

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

だから私は次のことを思いついた:

public static TResult IfNotNull<T, TResult>( this T input, Func<T, TResult> 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<T, TResult>( this T input, Func<T, TResult> action )
    where T : class
    where TResult : class
{ return input.IfNotNull( action, null ); }

これにより、次の構文が可能になります。

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 ) );

これにより、これらの呼び出しが簡略化されますが、この種の拡張メソッドをチェックインするかどうかはわかりません。コードが少し読みやすくなりますが、オブジェクトを拡張する必要があります。これはすべてに表示されますが、具体的に参照される名前空間に配置することもできます。

この例はかなり単純な例ですが、もう少し複雑な例では、2つのnull許容オブジェクトのプロパティを比較します。

if( ( obj1 == null && obj2 == null ) || 
    ( obj1 != null && obj2 != null && obj1.Property == obj2.Property ) )
    ...

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

このように拡張機能を使用する際の落とし穴は何ですか?他のコーダーは混乱する可能性がありますか?これは単に拡張機能の乱用ですか?


私がここで本当に欲しいのはコンパイラ/言語拡張だと思います:

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

//becomes
propertyValue1 = myObject?StringProperty;

これにより、複雑なケースがはるかに簡単になります。

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

//becomes
propertyValue1 = myObject?ObjectProp?StringProperty;

これは値型に対してのみ機能しますが、null許容の同等物を返すことができます。

int? propertyValue2 = myObject?ObjectProp?IntProperty;

//or

int propertyValue3 = myObject?ObjectProp?IntProperty ?? 0;
4

11 に答える 11

16

私たちは独立して、まったく同じ拡張メソッド名と実装を思いつきました: Null-propagating extension method . したがって、混乱や拡張メソッドの悪用ではないと思います。

次のように、チェーンを使用して「複数レベル」の例を記述します。

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

Microsoft Connectには、"?" を提案するクローズド バグがあります。この null 伝播を実行する新しい C# 演算子として。Mads Torgersen (C# 言語チーム) は、C# を実装しない理由を簡単に説明しました。

于 2008-10-10T14:32:08.443 に答える
15

拡張メソッドを含む、チェーンされたメンバーの別のソリューションを次に示します。

public static U PropagateNulls<T,U> ( this T obj
                                     ,Expression<Func<T,U>> expr) 
{  if (obj==null) return default(U);

   //uses a stack to reverse Member1(Member2(obj)) to obj.Member1.Member2 
   var members = new Stack<MemberInfo>();

   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;    
}

次に、anyがnullまたはnoneになる可能性がある場所でこれを行うことができます:

foo.PropagateNulls(x => x.ExtensionMethod().Property.Field.Method());
于 2008-09-28T02:00:46.100 に答える
11

オブジェクトへの参照が null かどうかを頻繁に確認する必要がある場合は、Null Object Patternを使用する必要があります。このパターンでは、オブジェクトがない場合に対処するために null を使用する代わりに、同じインターフェイスを使用して新しいクラスを実装しますが、適切な既定値を返すメソッドとプロパティを使用します。

于 2008-09-23T19:28:19.270 に答える
5

どうですか

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

より読みやすく書きやすい

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

Jafar Husain は、Expression Trees を使用してチェーン内の null をチェックするサンプル、Runtime macros in C# 3を投稿しました。

ただし、これには明らかにパフォーマンスへの影響があります。コンパイル時にこれを行う方法があればよいのですが。

于 2008-09-23T20:14:41.297 に答える
5

私はこのハックが大好きだと言わざるを得ません!

拡張メソッドが null チェックを意味しないことに気づいていませんでしたが、それは完全に理にかなっています。James が指摘したように、拡張メソッドの呼び出し自体は通常のメソッドよりも高価ではありませんが、これを大量に実行する場合は、ljorquera が提案した Null オブジェクト パターンに従うのが理にかなっています。または、null オブジェクトと ?? を使用するには 一緒。

class Class1
{
    public static readonly Class1 Empty = new Class1();
.
.
x = (obj1 ?? Class1.Empty).X;
于 2008-09-23T20:15:40.763 に答える
1

拡張メソッドは通常、nullインスタンスから呼び出されると誤解を招きますが、この場合の意図は非常に単純だと思います。

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

この静的メソッドが、intなどのnullになる可能性のある値型で機能することを確認したいと思いますか?

編集:コンパイラは、これらのどちらも有効ではないと言っています:

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

それ以外は、それのために行きなさい。

于 2008-09-23T23:39:36.170 に答える
1

知らない読者には、null 参照でメソッドを呼び出しているように見えます。これが必要な場合は、拡張メソッドを使用するのではなく、ユーティリティ クラスに配置することをお勧めします。


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

「ユーティリティ」。すりおろしますが、IMO はより少ない構文上の悪です。

また、チームの一員としてこれを開発している場合は、他の人が何を考え、何をしているのかを優しく尋ねてください。頻繁に使用されるパターンのコードベース全体での一貫性は重要です。

于 2008-09-23T20:24:45.140 に答える
1

コードが少し読みやすくなりますが、オブジェクトを拡張するという犠牲が伴います。これはあらゆるものに現れます。

実際には何も拡張していないことに注意してください (理論的なものを除く)。

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

以下のように記述されている場合とまったく同じように IL コードを生成します。

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

これをサポートするためにオブジェクトに追加される「オーバーヘッド」はありません。

于 2008-09-23T20:04:34.487 に答える
0

個人的には、すべての説明の後でも、これがどのように機能するのか思い出せません。

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

これは、C#の経験がないことが原因である可能性があります。しかし、私はあなたのコードの他のすべてを読んで理解することができました。明日、別の開発者が既存の言語に関する情報をあまり多くせずにコードをまったく新しい言語に変更できるように、コード言語に依存しないようにすることを好みます(特に些細なことについて)。

于 2008-09-23T19:13:18.870 に答える
0

http://www.epitka.blogspot.com/で説明されている myObject.NullSafe(x=>x.SomeProperty.NullSafe(x=>x.SomeMethod)) を使用した別のソリューションを次に示し ます。

于 2009-05-28T15:21:38.650 に答える