25

質問

それ自体の null 参照を処理できるクラスが必要です。これどうやってするの?拡張メソッドは、これを行うために考えられる唯一の方法ですが、C# について知らなかった気の利いたことがあれば聞いてみようと思いました。

Userというプロパティで呼び出されるクラスがありますIsAuthorized

User適切にインスタンス化された場合IsAuthorizedに実装があります。ただし、参照に null が含まれている場合、爆発する代わりに false を返すUserように呼び出したいと思います。IsAuthorized


解決

良い答えがたくさん。私は最終的にそれらのうちの3つを使用して問題を解決しました。

  1. Zaid Masud が提案した Null Object デザイン パターンを使用しました。
  2. それを構造体を使用するというBelmirisの提案と組み合わせたので、null参照を持つことはできませんでした
  3. そして、C# がこのように機能する理由と、それを実際に解決する方法について、Jon Hanna から素晴らしい説明を受けました。

残念ながら、私が受け入れた回答としてこれらの 1 つしか選択できないため、このページにアクセスしている場合は、これら 3 つすべてと、与えられた他の優れた回答のいずれかに賛成票を投じてください。

4

7 に答える 7

29

適切なオブジェクト指向ソリューションはどうですか? これこそが、Null Objectデザイン パターンの目的です。

IUser インターフェイスを抽出し、User オブジェクトにこのインターフェイスを実装させてから、(IUser も実装する) NullUser オブジェクトを作成し、IsAuthorized プロパティで常に false を返すことができます。

ここで、消費コードを変更して、User ではなく IUser に依存するようにします。クライアント コードは null チェックを必要としなくなります。

以下のコードサンプル:

public interface IUser
{
    // ... other required User method/property signatures

    bool IsAuthorized { get; }
}

public class User : IUser
{
    // other method/property implementations

    public bool IsAuthorized
    {
        get { // implementation logic here }
    }
}

public class NullUser : IUser
{
    public bool IsAuthorized
    {
        get { return false; }
    }
}

これで、コードは User ではなく IUser を返し、クライアント コードは IUser のみに依存します。

public IUser GetUser()
{
    if (condition)
    {
        return new NullUser(); // never return null anymore, replace with NullUser instead
    }
    return new User(...);
}
于 2012-08-15T15:43:37.267 に答える
18

ただし、ユーザー参照にnullが含まれている場合は、IsAuthorizedが爆発するのではなく常にfalseを返すようにします。

これIsAuthorizedは、が静的メソッドである場合にのみ実行できます。この場合、nullをチェックできます。これが、拡張メソッドがこれを実行できる理由です。これらは、静的メソッドを呼び出すための実際には異なる構文にすぎません。

インスタンスメソッドなどのメソッドまたはプロパティを呼び出すにIsAuthorizedは、インスタンスが必要です。インスタンスメソッド(プロパティゲッターを含む)を呼び出すだけでnull、例外がトリガーされます。例外はクラスによって発生するのではなく、(null)参照を使用しようとしたときにランタイム自体によって発生します。C#ではこれを回避する方法はありません。

于 2012-08-15T15:31:23.190 に答える
11

変数がnullの場合、それはオブジェクトを参照していないことを意味します。したがって、クラスメソッド内でnull参照を処理することは意味がありません(技術的には不可能だと思います)。

「IsAuthorized」を呼び出す直前または前のイベントをチェックして、nullでないことを保証する必要があります。

編集:それを行うための回避策を見つけることは悪いことです:それはプログラミング言語の「期待される」振る舞いではないので、誰かがその振る舞いを理解するのは混乱するでしょう。また、コードが問題(オブジェクトであるはずのnull値)を隠し、見つけにくいバグを作成する可能性もあります。それは言った:それは確かに悪い考えです。

于 2012-08-15T15:32:03.267 に答える
7

問題は、そのようなメソッドを作成することではありません。それはメソッドを呼び出すことです。if(this == null)コードにのテストを入れると、それは完全に有効です。ヒットすることが「不可能」であるという理由で、コンパイラーによって最適化できると思いますが、ありがたいことにそうではありません。

ただし、メソッドを呼び出すと、を介して実行されるcallvirtため、メソッドを直接呼び出すのではなく、仮想メソッドの場合と同じように、特定のインスタンスを呼び出すメソッドのバージョンが検出されます。これはnull参照では失敗するため、完全に優れたセルフnullテストメソッドは、呼び出される前に失敗します。

C#は意図的にこれを行います。エリック・ガンナーソンによれば、これはあなたにそうさせるのは少し奇妙だと彼らが思ったからです。

C++をモデルにした.NET言語に.NETと同じ会社が作成したC++コンパイラ*の両方で完全に許容できることをさせることがなぜ少し奇妙だと考えられたのか理解できませんでした。許可されなかったのは少し変だといつも思っていました。

クラスを呼び出す別の言語(F#またはIL)から何かを追加するか、それを使用Reflection.Emitして正常に機能するデリゲートを生成するために使用できます。たとえば、次のコードは、でGetHashCode定義されたバージョンを呼び出しますobject(つまり、GetHashCodeオーバーライドされた場合でも、オーバーライドは呼び出されません)。これは、nullインスタンスで安全に呼び出すことができるメソッドの例です。

DynamicMethod dynM = new DynamicMethod(string.Empty, typeof(int), new Type[]{typeof(object)}, typeof(object));
ILGenerator ilGen = dynM.GetILGenerator(7);
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Call, typeof(object).GetMethod("GetHashCode"));
ilGen.Emit(OpCodes.Ret);
Func<object, int> RootHashCode = (Func<object, int>)dynM.CreateDelegate(typeof(Func<object, int>));
Console.WriteLine(RootHashCode(null));

これの良い点の1つは、保持できるRootHashCodeため、1回だけビルドする必要があり(静的コンストラクターなど)、繰り返し使用できることです。

もちろん、これは他のコードにnull参照を介してメソッドを呼び出させることには価値がありません。あなたが提案するような拡張メソッドが唯一の賭けだからです。

もちろん、このC#の癖がない言語で書いている場合は、C#の人々ができるので、null参照を呼び出すための「デフォルト」の結果を取得するための代替手段を提供する必要があることにも注意してください。取得します。C#と同じように、一部の言語では大文字と小文字を区別できないため、パブリック名の大文字と小文字のみの違いは避けてください。

編集:あなたの質問IsAuthorizedが呼ばれている完全な例。投票は、それができると信じていない人がいることを示唆しているからです(!)

using System;
using System.Reflection.Emit;
using System.Security;

/*We need to either have User allow partially-trusted
 callers, or we need to have Program be fully-trusted.
 The former is the quicker to do, though the latter is
 more likely to be what one would want for real*/ 
[assembly:AllowPartiallyTrustedCallers]

namespace AllowCallsOnNull
{
  public class User
  {
    public bool IsAuthorized
    {
      get
      {
        //Perverse because someone writing in C# should be expected to be friendly to
        //C#! This though doesn't apply to someone writing in another language who may
        //not know C# has difficulties calling this.
        //Still, don't do this:
        if(this == null)
        {
          Console.Error.WriteLine("I don't exist!");
          return false;
        }
        /*Real code to work out if the user is authorised
        would go here. We're just going to return true
        to demonstrate the point*/
        Console.Error.WriteLine("I'm a real boy! I mean, user!");
        return true;
      }
    }
  }
  class Program
  {
    public static void Main(string[] args)
    {
      //Set-up the helper that calls IsAuthorized on a
      //User, that may be null.
      DynamicMethod dynM = new DynamicMethod(string.Empty, typeof(bool), new Type[]{typeof(User)}, typeof(object));
      ILGenerator ilGen = dynM.GetILGenerator(7);
      ilGen.Emit(OpCodes.Ldarg_0);
      ilGen.Emit(OpCodes.Call, typeof(User).GetProperty("IsAuthorized").GetGetMethod());
      ilGen.Emit(OpCodes.Ret);
      Func<User, bool> CheckAuthorized = (Func<User, bool>)dynM.CreateDelegate(typeof(Func<User, bool>));

      //Now call it, first on null, then on an object
      Console.WriteLine(CheckAuthorized(null));    //false
      Console.WriteLine(CheckAuthorized(new User()));//true
      //Wait for input so the user will actually see this.
      Console.ReadKey(true);
    }
  }
}

ああ、そしてこれにおける現実の実際的な懸念。C#の動作の良い点は、null参照の呼び出しでフェイルファストが発生し、途中でフィールドまたは仮想にアクセスするために失敗することです。これは、呼び出しを作成するときにnullインスタンスにあるかどうかを心配する必要がないことを意味します。ただし、完全にパブリックなメソッド(つまり、パブリッククラスのパブリックメソッド)で防弾を使用したい場合は、これに依存することはできません。メソッドのステップ1の後に常にステップ2が続くことが重要であり、ステップ2がnullインスタンスで呼び出された場合にのみ失敗する場合は、自己nullチェックが必要です。これが発生することはめったにありませんが、C#以外のユーザーには、上記の手法を使用しないとC#で再現できないバグが発生する可能性があります。

*ただし、これはコンパイラに固有です。C++標準IIRCでは定義されていません。

于 2012-08-15T16:19:44.303 に答える
5

代わりに構造体を使用できますか? その後、null になることはありません。

于 2012-08-15T15:37:42.933 に答える
3

有効なインスタンス参照がない場合、プロパティを参照できません。null 参照を使用してもプロパティを参照できるようにし、呼び出し元に null チェックの責任を負わせたくない場合、1 つの方法は の静的メソッドですUser

static bool IsAuthorized(User user)
{
    if(user!=null)
    {
        return user.IsAuthorized;
    }
    else
    {
        return false;
    }
}

次に、承認を確認する場合は、次の代わりに:

if(thisUser.IsAuthorized)

行う:

if(User.IsAuthorized(thisUser))

于 2012-08-15T15:35:13.113 に答える
2

これが機能する唯一の方法は、拡張メソッドまたは他の静的メソッドを使用してnull参照を処理する場合です。

NullReferenceExceptions(JavaheadsのNullPointerExceptions。ほぼ同義語)は、実際には存在しないオブジェクトインスタンスに属するメソッドを呼び出すようにコードが指示されたときに発生します。覚えておく必要があるのは、nullは実際にはオブジェクトではないということです。型の変数はnullに設定できますが、それは単に変数がインスタンスを参照しないことを意味します。

そこに問題があります。変数が、その型に関係なく(null許容型である限り)nullの場合、メソッドを呼び出すインスタンスはありません。インスタンスメソッドにはインスタンスが必要です。これは、プログラムがメソッドにアクセス可能なメンバーの状態。MyClassにMyFieldとMyMethod()があり、MyClassのnull参照でMyMethodを呼び出した場合、MyFieldの値は何ですか?

解決策は通常、静的スコープに移動することです。静的メンバー(およびクラス)は、実行時に1回インスタンス化されるため(通常、最初の参照の前のように、ジャストインタイム)、状態を持つことが保証されます。それらは常に状態を持っているので、いつでも呼び出すことができ、インスタンスレベルでは実行できない可能性のあることを実行することができます。C#で使用して、オブジェクトメンバーチェーンから値を返すことができるメソッドを次に示します。そうしないと、NREが発生する可能性があります。

public static TOut ValueOrDefault<TIn, TOut>(this TIn input, Func<TIn, TOut> projection, 
       TOut defaultValue = default(TOut))
    {
        try
        {
            var result = projection(input);
            if (result == null) result = defaultValue;
            return result;
        }
        catch (NullReferenceException) //most nulls result in one of these.
        {
            return defaultValue;
        }
        catch (InvalidOperationException) //Nullable<T>s with no value throw these
        {
            return defaultValue;
        }
    }

使用法:

class MyClass {public MyClass2 MyField;}
class MyClass2 {public List<string> MyItems; public int? MyNullableField;}

...
var myClass = null;
//returns 0; myClass is null
var result = myClass.ValueOrDefault(x=>x.MyField.MyItems.Count);
myClass = new MyClass();
//returns 0; MyField is null
result = myClass.ValueOrDefault(x=>x.MyField.MyItems.Count);
myClass.MyField = new MyClass2();
//returns 0; MyItems is null
result = myClass.ValueOrDefault(x=>x.MyField.MyItems.Count);
myClass.MyField.MyItems = new List<string>();
//returns 0, but now that's the actual result of the Count property; 
//the entire chain is valid
result = myClass.ValueOrDefault(x=>x.MyField.MyItems.Count);
//returns null, because FirstOrDefault() returns null
var myString = myClass.ValueOrDefault(x=>x.MyField.MyItems.FirstOrDefault());
myClass.MyField.MyItems.Add("A string");
//returns "A string"
myString = myClass.ValueOrDefault(x=>x.MyField.MyItems.FirstOrDefault());
//returns 0, because MyNullableField is null; the exception caught here is not an NRE,
//but an InvalidOperationException
var myValue = myClass.ValueOrDefault(x=>x.MyField.MyNullableField.Value);

このメソッドは、長いネストされた三項演算子を使用してユーザーに表示したり、計算に使用したりするために何か(何か)を生成する必要がある状況では価値がありますが、アクションを実行するためにこのパターンを使用することはお勧めしません(voidメソッド)。NREやIOEは破棄されないため、要求したことが実際に行われたかどうかはわかりません。trueまたはfalseを返す、および/またはスローされた例外(存在する場合)を含む出力パラメーターを持つ「TryPerformAction()」メソッドを使用して回避できる場合があります。しかし、そのような問題に直面する場合は、自分で試してみてください。

于 2012-08-15T17:43:52.237 に答える