web-dev-qa-db-ja.com

ゲッターは、セッターに渡されたものとは異なる値を返すことを許可されるべきですか?

たとえば、次のクラスがあるとします。

public class SomeClass
{
    //...

    private IEnumerable<SomeType> myEnumerable;

    public IEnumerable<SomeType> MyEnumerable
    {
        get => myEnumerable;
        set => myEnumerable = value ?? new List<SomeType>();
    }
}

このタイプの強制をセッター(またはゲッター)で実行することは良い習慣ですか、またはプロパティがフィールドとは異なる動作をしないようにすることがより重要です-たとえば、次のシナリオでは:

var obj = new SomeClass();
IEnumerable<SomeType> enumerable = GetEnumerable(); //returns null
obj.MyEnumerable = enumerable;
Assert.AreEqual(enumerable, obj.MyEnumerable); //wrong!  
1

ここにいくつかの組み合わせた質問があります。

  • セッターは受け取った値とは異なる値を格納できますか?

はい、それが正当な場合。ただし、referenceタイプの場合は、単にそれを返すのではなく、実際にデフォルト値を格納する必要があります。そうしないと、予期しない動作が発生する可能性があります。

  • ゲッターは、保存されているものとは異なる値を返すことができますか?

はい、それが正当な場合。

  • この現在の例は、これを行う必要がある状況ですか?

いいえ。この例では、まったく異なる問題の回避策としては不適切です。


なぜこの例でそれをすべきでないのか

コードの問題は、ソリューションが問題とは異なる場所に実装され、コードがばらばらになることです。

_public IEnumerable<SomeType> MyEnumerable
{
    get => myEnumerable;
    set => myEnumerable = value ?? new List<SomeType>();
}
_

ここでの「カスタム」ロジックは?? new List<SomeType>()です。それ以外はすべて、プロパティのデフォルトの実装です。このコードは、「null値をデフォルト値に変換する必要がある」と論理的に同等です。

null値をデフォルト値に変換する必要がある場合。あなたが自問すべき最初の質問はnull値がどこから来ているかです、これはここにあります:

_IEnumerable<SomeType> enumerable = GetEnumerable(); //returns null
_

そして今、ばらばらの問題/解決策を見る:

  • 問題GetEnumerable()メソッド内で発生します。
  • solutionは、GetEnumerable()の戻り値を格納するために使用されるクラスのプロパティ内で発生します。

これにはいくつかの理由で問題があります。

  • GetEnumerable()が他の場所で使用されており、値をSomeOtherClassに格納している場合、このクラスはも必要です潜在的なnull値を処理します。これは、DRYの原則に違反しています。
  • また、SomeClassGetEnumerable()メソッドに不必要に関連付けます。これは、GetEnumerable()メソッドの出力に(かなり)固有の検証が含まれているためです。これは、懸念の分離に違反します。

最適なソリューション、優先順

  1. GetEnumerable()メソッドを変更します。

理想的には、メソッドがnullを返さないようにする必要があります。これには例外がありますが、例には当てはまりません。
例えば。 SingleOrDefault()のようなメソッドは、null意味のある戻り値であることを多少明示的に指定しています。 SingleOrDefault()の代わりにSingle()を使用することにより、開発者は、例外ではなくnull(または定義済みのデフォルト)値を使用することを明示的に指定しました。したがって、開発者はawareであり、null値に遭遇する可能性があります(別のデフォルト値を指定した場合を除く)。

これは単にGetEnumerable()メソッド本体で修正されています。 returnステートメントが次の場合:

_return myVariable;
_

単に次のように変更できます。

_return myVariable ?? new List<SomeType>();
_

問題は解決しました。null値は返されません。

ただし、メソッド自体にアクセスできない可能性があります。これが外部ライブラリからのメソッドである場合。

  1. 返されたnullをすぐに処理します。

簡単に言うと、メソッド呼び出しとnull処理をインライン化します。

_IEnumerable<SomeType> enumerable = GetEnumerable() ?? new List<SomeType>();
_

メソッドはまだnullを返すことができますが、これは制御できませんが、すぐにnullを空のリスト。以降のすべてのコードでは、null値が渡されたかどうかを評価する必要がなくなりました。

サイドコメント
このメソッドが多くの異なる場所で使用されている場合、null値を処理する別のメソッドでメソッドをラップすることは価値があります。

_public List<SomeType> GetEnumerableOrDefault()
{
     return GetEnumerable() ?? new List<SomeType>();
}
_

このように、?? new List<SomeType>()を呼び出すたびにGetEnumerable()を置く必要はありません。


ゲッター/セッターの値を変更したいその他の場合:

これは、値をいじりたい場合の使用例を改善するためのものです。

  • 非論理的な価値の防止、例えば負の年齢:

例えば.

_public int Age
{
    get => _age;
    set => _ age = value < 0 ? 0 : value;
}
_

注:現代の標準では、これはビジネスロジックをプロパティに入れると見なされます。これは眉をひそめています。ただし、このようなアプローチを使用するためにsimpleアプリケーションに対して引数を作成できます。

  • プロパティが初期化されていない場合のnull参照の防止:

例えば.

_public string Name
{
    get => _name ?? "John Doe";
    set => _name = value;
}
_

注:

  • これを使用する良い理由を見つけるのに苦労しています。値型の場合、nullは通常問題になりません。参照型の場合、フィールドにデフォルトオブジェクトを格納しないと危険です。文字列はこれの例外となる可能性がありますが、文字列の場合は、空の値もチェックする必要がある場合があります。そのため、現在、goodのユースケースを考えることはできません。
  • 多くの場合、プロパティはコンストラクタで初期化されているか、初期化する必要があります。一般に、問題はnull値を設定することを決定した人にあります。これは、事後にnull値を受け取る不運な呼び出し元とは対照的です。
  • 文字列の場合、より賢明なアプローチは空の文字列もチェックすることです:get => String.IsNullOrWhitespace(_name) ? "John Doe" : _name;
  • これを参照タイプに使用しないでください。値がプロパティ内にあるがpretend、外部の呼び出し元に予期しない動作を引き起こすことになりますが、格納したことはありません。
3
Flater

私にとって、開発者が書くのは混乱するようです

someObject.List = null;
someObject.Add(item);

この種の場合は例外をスローする方が理にかなっています。開発者は書くべきです

someObject.List = new List<SomeType>();
someObject.List.Add(item);

またはさらに良いことに、

someObject.List.Clear();
someObject.List.Add(item);

だから私はあなたのプロパティを実装するより標準的な方法は次のようになると思います:

public class SomeClass
{
    private IEnumerable<SomeType> myEnumerable = new List<SomeType>();

    public IEnumerable<SomeType> MyEnumerable
    {
        get => myEnumerable;
        set => {
                   if (value == null) throw new ExceptionOfSomeKind();
                   myEnumerable = value;
               }
    }
}

またはこれ:

public class SomeClass
{
    //This is readonly now-- can be set only when instantiated
    private readonly List<SomeType> myEnumerable = new List<SomeType>();

    public List<SomeType> MyList
    {
        get => myEnumerable;
        //Notice no setter!!!
    }
}
2
John Wu

ゲッターとセッターでプロパティを使用する理由は、渡されたものを常に返す必要がないためです。

そうでなければ、なぜフィールド以外のものを使用するのですか?

1
Pieter B