21

私はこのコードが私のコンストラクターでたくさん現れることに気づきました:

if (someParam == null) throw new ArgumentNullException("someParam");
if (someOtherParam == null) throw new ArgumentNullException("someOtherParam");
...

いくつかのものが注入され、すべてnull以外でなければならないコンストラクターがいくつかあります。誰かがこれを合理化する方法を考えることができますか?私が考えることができる唯一のことは次のとおりです。

public static class ExceptionHelpers
{
   public static void CheckAndThrowArgNullEx(IEnumerable<KeyValuePair<string, object>> parameters)
   {
      foreach(var parameter in parameters)
         if(parameter.Value == null) throw new ArgumentNullException(parameter.Key);
   }
}

ただし、その使用法は次のようになります。

ExceptionHelper.CheckAndThrowArgNullEx(new [] {
    new KeyValuePair<string, object>("someParam", someParam),
    new KeyValuePair<string, object>("someOtherParam", someOtherParam),
    ... });

...これはコードの合理化にはあまり役立ちません。タプルのGTPは共変ではないため(IEnumerableのGTPは共変ですが)、KVPの代わりにTuple.Create()は機能しません。何か案は?

4

16 に答える 16

25

C#7の更新

null合体演算子でthrow式を使用できます。そのページの例を次に示します。

public string Name
{
    get => name;
    set => name = value ?? 
        throw new ArgumentNullException(paramName: nameof(value), message: "New name must not be null");
}

元の回答

個人的には、ThrowIfNullextensionメソッドを使用しています。誰にクレジットするかはわかりませんが、私は間違いなくそれを発明しませんでした。戻り値を使用して割り当てを実行できるので便利です。

public static T ThrowIfNull<T>(this T argument, string argumentName)
{
    if (argument == null)
    {
        throw new ArgumentNullException(argumentName);
    }
    return argument;
}

使用法:

this.something = theArgument.ThrowIfNull("theArgument");
// or in C# 6
this.something = theArgument.ThrowIfNull(nameof(theArgument));

(nullインスタンスで拡張メソッドを呼び出すのは変だと思う人もいますが)

一度に複数の引数を本当にチェックしたい場合、次のparamsような署名を使用すると、例がより合理化される可能性があります。

public static void CheckAndThrowArgNullEx(params object[] argsAndNames)
{
    for (int i = 0; i < argsAndNames.Length; i += 2)
    {
        if (argsAndNames[i] == null)
        {
            string argName = (string)argsAndNames[i + 1];
            throw new ArgumentNullException(argName);
        }
    }
}

使用法は次のようになります。

CheckAndThrowArgNullEx(arg1, "arg1", arg2, "arg2");
// or in C# 6
CheckAndThrowArgNullEx(arg1, nameof(arg1), arg2, nameof(arg2));

考え直してみると、KeithSがコメントで述べているように、次のparams object[]ように使用するよりも、これを一連のオーバーロードとして実装する方がよいでしょう。

static void Check(object arg1, string arg1Name) { ... }
static void Check(object arg1, string arg1Name, object arg2, string arg2Name) { ... }
// and so on...
于 2012-08-20T19:56:34.510 に答える
13

これを試してください:1行。

accounts = accounts ?? throw new ArgumentNullException(nameof(accounts));

また、を使用nameof()します。変数の名前が変更された場合は、すべての「変数」を探す必要はありません。そうさせnameof()てください。

于 2018-03-06T17:03:00.197 に答える
8

.NET6以降

.NETAPIには新しいメソッドがあります ArgumentNullException.ThrowIfNull(someParameter)

この方法は、おそらくあなたが得ることができる最良の選択肢です。

于 2021-11-06T08:19:48.477 に答える
7

これを行うにはいくつかの方法があります。

オプションA:

関数を2つに分割します-検証と実装(この例はJon SkeetのEduLinqで見ることができます)。

オプションB:

パラメータがnull以外であることを期待するコードコントラクトを使用します。

オプションC:

コードウィービングなどのアスペクト指向テクノロジーを使用して、これらのチェックをアスペクトに抽出します。(Jトーレスが答えたように)。

オプションD:

CodeInChaosがコメントしたように、 Spec#を使用します。

オプションE:

???

于 2012-08-20T19:31:26.727 に答える
5

ほとんどの皆さんのためのアップティック。あなたの答えは、私が最終的に到達した解決策に貢献しました。それは、断片を組み込んでいますが、最終的にはそれらすべてとは異なります。

特定の形式のラムダ式を処理する静的メソッドをいくつか作成しました(編集-小さな変更。メソッドをジェネリックにすることはできません。または、すべての式が同じ型を返す必要があります。代わりに、Funcは問題ありませんが、追加の条件があります。 GetNameメソッドでキャストをアンラップします):

public static class ExpressionReader
{
    /// <summary>
    /// Gets the name of the variable or member specified in the lambda.
    /// </summary>
    /// <param name="expr">The lambda expression to analyze. 
    /// The lambda MUST be of the form ()=>variableName.</param>
    /// <returns></returns>
    public static string GetName(this Expression<Func<object>> expr)
    {
        if (expr.Body.NodeType == ExpressionType.MemberAccess)
            return ((MemberExpression) expr.Body).Member.Name;

        //most value type lambdas will need this because creating the 
        //Expression from the lambda adds a conversion step.
        if (expr.Body.NodeType == ExpressionType.Convert
                && ((UnaryExpression)expr.Body).Operand.NodeType 
                     == ExpressionType.MemberAccess)
            return ((MemberExpression)((UnaryExpression)expr.Body).Operand)
                   .Member.Name;

        throw new ArgumentException(
           "Argument 'expr' must be of the form ()=>variableName.");
    }
}

public static class ExHelper
{
    /// <summary>
    /// Throws an ArgumentNullException if the value of any passed expression is null.
    /// </summary>
    /// <param name="expr">The lambda expressions to analyze. 
    /// The lambdas MUST be of the form ()=>variableName.</param>
    /// <returns></returns>
    public static void CheckForNullArg(params Expression<Func<object>>[] exprs)
    {
        foreach (var expr in exprs)
            if(expr.Compile()() == null)
                throw new ArgumentNullException(expr.GetName());
    }
}

...このように使用することができます:

//usage:

ExHelper.CheckForNullArg(()=>someParam, ()=>someOtherParam);

これにより、サードパーティのツールを使用せずに、ボイラープレートを1行に減らすことができます。ExpressionReader、つまり例外生成メソッドは、呼び出し元でコンパイルされる()=> variableNameの形式のラムダで機能します。つまり、少なくともローカル変数、パラメーター、インスタンスフィールド、およびインスタンスプロパティで機能します。静力学で動作するかどうかは確認していません。

于 2012-08-20T20:41:31.777 に答える
3
public class TestClass
{
    public TestClass()
    {
       this.ThrowIfNull(t=>t.Str, t=>t.Test);
       //OR
       //this.ThrowIfNull(t => t.X)
       //    .ThrowIfNull(t => t.Test);
    }
    string Str = "";
    public TestClass Test {set;get;}
}


public static class SOExtension
{
    public static T ThrowIfNull<T>(this T target, params Expression<Func<T, object>>[] exprs)
    {
        foreach (var e in exprs)
        {
            var exp = e.Body as MemberExpression;
            if (exp == null)
            {
                throw new ArgumentException("Argument 'expr' must be of the form x=>x.variableName");
            }

            var name = exp.Member.Name;
            if (e.Compile()(target) == null)
                throw new ArgumentNullException(name,"Parameter [" + name + "] can not be null");

        }
        return target;
    }
}
于 2012-08-20T19:57:30.387 に答える
3

C#7では、次のように実行できます。

_ = someParam ?? throw new ArgumentNullException(nameof(someParam));

リリースの最適化後、次のようになります。

if (someParam == null)
    throw new ArgumentNullException(nameof(someParam));
于 2020-03-19T15:19:18.603 に答える
3

C#10では、これを行うことができます。

ArgumentNullException.ThrowIfNull(z);

そして、このエラーが発生します:

System.ArgumentNullException: Value cannot be null. (Parameter 'z')
   at System.ArgumentNullException.Throw(String paramName)
   at System.ArgumentNullException.ThrowIfNull(Object argument, String paramName)
   at ConsoleApp1.SomeClass.Join(String a, String b)

内部的には、新しいCallerArgumentExpression属性を使用します。

于 2021-11-28T11:48:43.720 に答える
2

サードパーティのユーティリティに反対していない場合、PostSharpはそのような検証を挿入するためのクリーンな方法を提供します。 このブログ投稿は、問題の解決策を提供します。

更新:PostSharp3の新しい検証パラメーター機能を参照してください

于 2012-08-20T19:31:27.640 に答える
1

すでに多くの有効な解決策がありますが、ここに私の見解があります:

using System.Diagnostics;
using System.Reflection;

public SomeConstructor(int? par1, int? par2, string par3)
{
    CheckThrowNull(par1, par2, par3);
    //rest of constructor code...
}

///<param name="values"> Values must be given in order </param>
public static void CheckThrowNull(params object[] values)
{
    StackTrace stackTrace = new StackTrace();
    ParameterInfo[] parameters = stackTrace.GetFrame(1).GetMethod().GetParameters(); //get calling method's parameters (or constructor)
    if (parameters.Length != values.Length)
    {
        throw new ArgumentException("Incorrect number of values passed in");
    }
    for (int i = 0; i < parameters.Length; i++)
    {
        if (values[i] == null)
        {   
            //value was null, throw exception with corresponding parameter name
            throw new ArgumentNullException(parameters[i].Name);
        }
    }
}

一般的な考え方は、2つの並列配列が確立され、1つはParameterInfo型で、もう1つはパラメーターの値を含むというものです。後者は、パラメータ値がリフレクションを介して簡単に(そして不可能だと思いますが)取得できないため、渡す必要があります。期限が来ているところにクレジットを与えるために、私はここで呼び出しメソッドを取得する方法を見つけました:http ://www.csharp-examples.net/reflection-calling-method-name/

個人的には、デバッグ以外はSystem.Diagnosicsを使用するのが好きではないので、呼び出しコードを次のように少し変更します。

CheckThrowNull(MethodBase.GetCurrentMethod(), par1, par2, par3);

と方法は

CheckThrowNull(MethodBase method, params object[] values)
{
    ParameterInfo[] parameters = method.GetParameters();
    //rest of code same
}

欠点は、少し拡張できないことであり、引数の一部だけがnullであるかどうかを簡単に確認することはできません。

于 2012-08-20T20:33:48.223 に答える
1

拡張メソッドはどうですか?

public static void ThrowExceptionIfNull(this object argument, string argumentName)
{
    if(argument == null)
        throw new ArgumentNullException(argumentName);
} 

次に、コードは少なくとももう少し流暢に読みます。

someParam.ThrowExceptionIfNull("someParam");

それ以外の場合は、機能を分割するか、AOP(PostSharpなど)を使用することに同意します。

于 2012-08-20T19:34:45.373 に答える
1

まあ、定型文は避けるのが難しいです。C#とVisual Studioの代わりにBertrandMeyersのEiffelプログラミング言語EiffelStudioの使用に切り替えて、 「契約による設計」の練習を開始することができます。

Eiffelは最近CLRに完全に準拠しています。

于 2012-08-20T19:42:03.507 に答える
1

引数名の抽出の複数のバリエーションを使用してベンチマークアプリケーションを作成しました(匿名クラス+リフレクション/ MemberExpression / Funcなどを介して)

ベンチマークソースへのGithubリンク:https ://github.com/iXab3r/NullCheckCompetition

最速の方法は匿名クラスを使用することであるという結果が得られました。

.NET 40 / X64

失敗(つまり、引数がnullで、名前抽出メソッドが実行されます)

  • 失敗AnonymousClass67.87ns _
  • DoubleLambda643.98nsに失敗
  • 失敗LazyAnonymousClass69.36ns
  • RawCheck1.08nsに失敗
  • SingleLambda643.27nsに失敗します

成功(つまり、引数がnullではない)

  • SuccessAnonymousClass 6.33 ns
  • SuccessDoubleLambda 8.48 ns
  • SuccessLazyAnonymousClass 8.78 ns
  • SuccessRawCheck 1.08 ns
  • SuccessSingleLambda 628.28 ns
于 2015-07-06T18:52:20.187 に答える
0

上記のほとんどは大丈夫だと思いますが、どれもあなたがすでに持っているものを実際に改善するものではないので、私はKIS、Keep It Simpleに行き、それがあなたが始めたものです。

クリーンで、非常に読みやすく、高速です。少し長いのは

于 2017-02-09T08:28:47.573 に答える
0

C#10の更新(.NET> = 6):

_person = person.ThrowIfNull();

public static T ThrowIfNull<T>(this T? argument, string? message = default, [CallerArgumentExpression("argument")] string? paramName = default)
{
    return argument ?? throw new ArgumentNullException(paramName, message);
}

参照: https ://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/caller-argument-expression

于 2021-11-15T15:04:32.027 に答える
-1

実際には、Expressionタイプを経由せずに、ラムダ式から引数名を取得することができます。これがその方法です。

static void SampleMethod(string arg1)
{
    ThrowIfNull(() => arg1);
    // continue to other normal stuff here...
}

public static void ThrowIfNull<T>(Func<T> lambda) 
    where T : class
{
    if (lambda() == null)
    {
        throw new ArgumentNullException(lambda.Target.GetType().GetFields()[0].Name);
    }
}
于 2016-07-26T10:43:06.057 に答える