C# における null 許容型の代替
-
22-08-2019 - |
質問
一連の数値データを処理するアルゴリズムを作成していますが、場合によっては、その系列の値が null である必要があります。ただし、このアプリケーションはパフォーマンスが重要であるため、null 許容型の使用を避けてきました。私はアルゴリズムのパフォーマンス テストを行って、Null 許容型と非 Null 許容型を使用した場合のパフォーマンスを具体的に比較しました。最良のシナリオでは、Null 許容型は 2 倍遅くなりますが、はるかに悪い場合がよくあります。
最もよく使用されるデータ型は double で、現在 null の代わりに選択されているのは double.NaN です。ただし、これが NaN 値の正確な使用目的ではないことは理解しています。そのため、これに予見できない問題があるかどうか、またベスト プラクティスは何かがわかりません。
特に次のデータ型に最適な null 代替が何かを知ることに興味があります。double/float、10 進数、DateTime、int/long (ただし、他のものも大歓迎です)
編集:パフォーマンスに関する要件を明確にする必要があると思います。これらのアルゴリズムを通じて大量の数値データが一度に処理されると、数時間かかります。したがって、たとえば 10 ミリ秒と 20 ミリ秒の差は通常は重要ではありませんが、このシナリオでは実際にかかる時間に大きな影響を与えます。
解決
Nullable<T>
を除外した場合、さて、あなたは、ドメイン値が残されています。これはの理想的なのではないですが、それはどちらかの珍しいことではありません - 例えば、メインフレームワークコードの扱いの多くはヌルと同じDateTime.MinValue
。これは、少なくとも共通の価値観から遠く離れたダメージを移動...
を編集
何 だから、多分 もちろん、符号なしのデータのために、あなたは(ゼロを避ける!!!) 個人的に、私はおそらく、あなたのNaN
はありません。ここで、.MinValue
を使用する - しかし、ちょうどあなたが誤って、が.MaxValue
が必要になります。Nullable<T>
コードを最適化する方法があるかもしれません...より安全に私の意図を表現するようNullable<T>
を使用しようと思います。そしてまた - ?あなたはおそらくそれがNullable<T>
よりもはるかに高速ではありません、あなたがする必要があるすべての場所でマジックナンバーをチェックしてきました。
他のヒント
I多少この特定のエッジケースにGravellに同意できない:ヌル編変数「が定義されていない」とみなされ、それが値を持っていません。でもマジックナンバーが、マジックナンバーとあなたはそれがすべての突然の「有効」値になったときにマジックナンバーは将来的にあなたを悩ま常にすることを考慮に入れる必要があり:だから知らせるために使用されているもの、それはOKです。 Double.NaNを使用すると、そのために恐れる必要はありません:それは、有効な二重になろうとしてたことがないです。けれども、あなたはダブルスのシーケンスの意味でのNaNのが唯一の「定義されていません」のマーカーとして使用できることを考慮しなければならない、あなたは明らかに、同様の配列中のエラーコードとして使用することはできません。
だから、何でも「未定義の」マークするために使用されます:それはその特定の値が「未定義」の値とみなされ、それが将来的に変更されないことを値の集合の文脈で明確にする必要があります。
のNullableは、限り、あなたは結果を考慮としては、NaNを使用し、あなたの面倒を与える、または任意の他場合:選択した値は「未定義」とそれが滞在する表します。
私は NaN を null
価値。私はこれにまったく満足していません - あなたと同じような理由で:何が問題になるかわからない。これまでのところ実際の問題は発生していませんが、次の点に注意してください。
NaN 演算 - ほとんどの場合、「NaN プロモーション」は良いことですが、必ずしも期待通りになるとは限りません。
比較 - NaN を同等に比較したい場合、値の比較はかなり負荷が高くなります。いずれにせよ、浮動小数点数が等しいかどうかをテストするのは簡単ではありませんが、順序 (a < b) は非常に醜いものになる可能性があります。なぜなら、nan は通常の値より小さく、時には大きくする必要があるからです。
コードの感染 - 正しくするためには NaN の特定の処理を必要とする算術コードがたくさんあります。したがって、パフォーマンス上の理由から、「NaN を受け入れる関数」と「受け入れない関数」が存在することになります。
その他の非有限 NaN は唯一の非有限値です。心に留めておかなければなりません...
浮動小数点の例外 無効にしても問題ありません。誰かがそれを可能にするまで。実話:ActiveX コントロールでの NaN の静的初期化。InnoSetup を使用するようにインストールを変更するまでは、それほど怖いことではありません。InnoSetup は、デフォルトで FPU 例外が有効になっている Pascal/Delphi(?) コアを使用します。理解するのに時間がかかりました。
つまり、全体としては深刻なことは何もありませんが、NaN についてはそれほど頻繁に考慮したくないのです。
パフォーマンスやリソースの制約がある (ことが証明されている) 場合を除き、私は Nullable 型をできるだけ頻繁に使用します。1 つのケースとして、時折 NaN を含む大きなベクトル/行列、または名前付きの個別の値の大きなセットが考えられます。 デフォルトの NaN 動作は正しい場合.
あるいは、ベクトルと行列のインデックス ベクトル、標準の「疎行列」実装、または別のブール/ビット ベクトルを使用することもできます。
部分的な答えます:
floatとdoubleはNaN(非数)を提供します。 NaNは、仕様ごとに、NaNで以来、少しトリッキーです!= NaNに。あなたは数がNaNであるかどうかを知りたい場合は、Double.IsNaN()を使用する必要があります。
また、 2進浮動小数点および.NET を参照してください。
たぶん、パフォーマンスの大幅な低下が起こります。
ダブル+値が指定されているかどうかを伝えるブールで構造体を使用してみてください。
一つは、独自の構造を定義することによりNullable<T>
に関連したパフォーマンスの低下のいくつかを回避することができます。
struct MaybeValid<T>
{
public bool isValue;
public T Value;
}
所望であれば、一つのサブ最適な性能を得ることができるようなもののコンストラクタ、またはT
するMaybeValid<T>
から変換演算子などが、乱用を定義することができます。 1は、不要なデータのコピーを回避した場合に露出フィールドの構造体が効率的な場合があります。一部の人々は暴露フィールドの概念時に難色を示すかもしれないが、彼らは、プロパティという大規模な、より効率的にすることができます。 T
を返す関数はT
を使用して、その戻り値を保持するタイプのMaybeValid<Foo>
の変数を持っている必要があります場合は、単純なもののサイズが返される4によって増加します。これとは対照的に、Nullable<Foo>
を使用すると、関数が最初Foo
を計算し、その後Nullable<Foo>
のコンストラクタにそのコピーを渡す必要があります。さらに、Nullable<Foo>
を返すことは、それはそれで有用な何かを行うことができます前に、返された値を使用したい任意のコードがタイプFoo
の(変数または一時的な)保管場所への少なくとも一つの余分なコピーを作成する必要があることを必要とします。対照的に、コードは、他の変数と同じ効率について型Value
の可変のFoo
フィールドを使用することができます。