ジェネリックがパッケージングから私たちを救う方法

メソッドを呼び出すときに、nullチェックを実行することがよくあります。 誰かが別のメソッドでチェックアウトすると、コードはよりきれいに見え、次のようになります。







public void ThrowIfNull(object obj) { if(obj == null) { throw new ArgumentNullException(); } }
      
      





そして、このようなチェックで興味深いのは、ジェネリックを使用できるため、オブジェクト属性の大規模な使用が見られることです。 メソッドを汎用に置き換えて、パフォーマンスを比較してみましょう。







テストする前に、オブジェクト引数のもう1つの欠点があります。 値の型をnullにすることはできません(Nullable型はカウントしません)。 ThrowIfNull(5)などのメソッド呼び出しは無意味ですが、引数の型はオブジェクトであるため、コンパイラはメソッドの呼び出しを許可します。 私にとっては、これはコードの品質を低下させます。状況によっては、パフォーマンスよりもはるかに重要です。 この動作を取り除き、メソッドのシグネチャを改善するには、ジェネリックメソッドを2つに分割して、制約を示す必要があります。 問題は、Nullable制約を指定できないことですが、struct制約を使用してNullable引数を指定できます。







パフォーマンスのテストを開始し、 BenchmarkDotNetライブラリを使用します。 属性を添付して実行し、結果を確認します。







 public class ObjectArgVsGenericArg { public string str = "some string"; public Nullable<int> num = 5; [MethodImpl(MethodImplOptions.NoInlining)] public void ThrowIfNullGenericArg<T>(T arg) where T : class { if (arg == null) { throw new ArgumentNullException(); } } [MethodImpl(MethodImplOptions.NoInlining)] public void ThrowIfNullGenericArg<T>(Nullable<T> arg) where T : struct { if(arg == null) { throw new ArgumentNullException(); } } [MethodImpl(MethodImplOptions.NoInlining)] public void ThrowIfNullObjectArg(object arg) { if(arg == null) { throw new ArgumentNullException(); } } [Benchmark] public void CallMethodWithObjArgString() { ThrowIfNullObjectArg(str); } [Benchmark] public void CallMethodWithObjArgNullableInt() { ThrowIfNullObjectArg(num); } [Benchmark] public void CallMethodWithGenericArgString() { ThrowIfNullGenericArg(str); } [Benchmark] public void CallMethodWithGenericArgNullableInt() { ThrowIfNullGenericArg(num); } } class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<ObjectArgVsGenericArg>(); } }
      
      





方法 平均 エラー Stddev
CallMethodWithObjArgString 1.784 ns 0.0166 ns 0.0138 ns
CallMethodWithObjArgNullableInt 124.335 ns 0.2480 ns 0.2320 ns
CallMethodWithGenericArgString 1.746 ns 0.0290 ns 0.0271 ns
CallMethodWithGenericArgNullableInt 2.158 ns 0.0089 ns 0.0083 ns


私たちの一般的なnullable型は2000倍速く動作しました! そしてすべては、悪名高いパッケージング(ボクシング)によるものです。 CallMethodWithObjArgNullableIntを呼び出すと、nullable-intが「パック」されてヒープに配置されます。 パッケージングは​​非常に高価な操作であり、この方法ではパフォーマンスも低下します。 したがって、ジェネリックを使用すると、パッケージ化を回避できます。







したがって、次の理由により、ジェネリック引数はオブジェクトよりも優れています。







  1. パッケージングから節約
  2. 制限を使用するときにメソッドのシグネチャを改善できます


更新しました。 コメントを寄せてくれたzelyony habrayuzerに感謝します。 インラインのメソッドでは、より正確な測定のために、 MethodImpl(MethodImplOptions.NoInlining)属性が追加されました。








All Articles