2016-07-19 34 views
9

IEnumerable<object>を使用することをお勧めします。IEnumerableではなく、range.Skip(2)などのLINQ拡張メソッドが定義されています。しかし、私はIEnumerableを使用することをお勧めします。T[]は暗黙的にIEnumerableに変換されます。Tが参照タイプまたは値タイプであるかどうか。後者の場合、ボクシングは含まれておらず、良好である。その結果、私はIEnumerable range = new[] { 1, 2, 3 }を行うことができます。両方の世界のベストを組み合わせることは不可能です。とにかく、私はIEnumerableに落ち着き、LINQメソッドを適用する必要があるときに何らかのキャストを行いました。IEnumerableからIEnumerableへのキャスト<object>

thisスレッドから、私はrange.Cast<object>()が仕事をすることができることを知ります。しかし、私の意見では不必要なパフォーマンスオーバーヘッドが発生します。私は(IEnumerable<object>)rangeのような直接コンパイル時のキャストを実行しようとしました。私のテストによると、それは参照型の型では機能しますが、値型では機能しません。何か案は?

参考になると、質問にはthisGitHub問題があります。次のように私が使用したテストコードがある:これは、基本的に最初の場所でIEnumerable<object>よりも他には何もしません

public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) 
    { 
     IEnumerable<TResult> enumerable = source as IEnumerable<TResult>; 
     if (enumerable != null) 
     return enumerable; 
     if (source == null) 
     throw Error.ArgumentNull("source"); 
     return Enumerable.CastIterator<TResult>(source); 
    } 

    private static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source) 
    { 
     foreach (TResult result in source) 
     yield return result; 
    } 

:逆コンパイル、その拡張子後

static void Main(string[] args) 
{ 
    // IEnumerable range = new[] { 1, 2, 3 }; // won't work 
    IEnumerable range = new[] { "a", "b", "c" }; 
    var range2 = (IEnumerable<object>)range; 
    foreach (var item in range2) 
    { 
     Console.WriteLine(item); 
    } 
} 
+0

あなたが本当に 'IEnumerableを'意味するか、または'IEnumerable 'を意味しますか?前者の場合、なぜ「」ではなく「」を使用しますか? – Maarten

+0

@Maarten「IEnumerable 」というのは、一般的なケースを扱い、さまざまな要素タイプをサポートする必要があるためです。 – Lingxi

+3

なぜ最初に 'IEnumerable'を使って作業するのですか?どうして 'IEnumerable range = new [] {1、2、3};'代わりに? - Btw。 'IEnumerable' *はしますか?あなたが(非ジェネリックな) 'IEnumerable'を' foreach 'すると、 'object'が得られます。一方、* generic * enumerable( 'IEnumerable ')は* not *ボックスになります。 – Corak

答えて

0

、ソースはこれを示しました。

あなたは次のように述べています

を私のテストによると、それが参照要素型のためではなく、 値型のために動作します。

どのようにテストしましたか?

+0

更新された質問にテストコードを示しました。 – Lingxi

1

IEnumerable<T>は汎用インターフェースです。コンパイル時に知られているジェネリックと型だけを扱っている限り、汎用メソッドを記述しているか、正しい型があるかによって、IEnumerable<int>またはIEnumerable<T>のどちらかを使用しても意味がありません。すでに知られている。それらに合わせてIEnumerableを見つけようとしないでください - まず最初に正しいものを使用してください - それが不可能であることは非常にまれですが、ほとんどの場合、単にオブジェクトの設計が悪い結果です。

IEnumerable<object>にキャストできない理由は多少驚くかもしれませんが、実際には非常に簡単です。値型は多相ではないため、共分散はサポートされていません。間違えてはいけない - IEnumerable<string>IEnumerable<object>を実装していない - あなたはIEnumerable<object>IEnumerable<string>をキャストすることができる唯一の理由は、IEnumerable<T>共変であるということです。

「驚くべき、しかし明らかな」という単なる面白いケースです。 intobjectから派生しているので、それは驚くべきことです。 intが本当にはそれがは「実際のオブジェクト由来INT」を作成し、ボクシングと呼ばれるプロセスを通じてobjectをキャストすることができるにもかかわらず、objectから派生していないため。そして、まだ、それは、明らかです

+0

異なる要素型(つまり、ランタイムポリモーフィズム)をサポートする必要があるので、IEnumerable を使用する必要があります。 – Lingxi

+0

@Lingxiその場合、最後の手段として 'IEnumerable 'を使い、 '.Cast 'を実行してください。参照型の場合は、単純型変換(共変型であるため)を行い、値型の場合はIEnumerableと同じことを行います。 – Luaan

5

私のテストによると、リファレンスエレメントタイプでは動作しますが、 値タイプでは動作しません。

正しい。これは、IEnumerable<out T>が共変種であり、co-variance/contra-variance is not supported for value typesであるためです。

私はその範囲を知るようになりました。キャスト()は仕事をすることができます。しかし、それは 私の意見では不必要なパフォーマンスのオーバーヘッドが発生します。

IMO与えられた価値タイプのコレクションを持つオブジェクトのコレクションが必要な場合、パフォーマンスコスト(ボクシングによってもたらされる)は避けられません。 IEnumerable.GetEnumeratorは、.Currentプロパティがobjectを返すIEnumeratorを提供するため、非汎用のIEnumerableを使用するとボクシングは避けられません。私は常にIEnumerableの代わりにIEnumerable<T>を使用することをお勧めします。だからちょうど.Castメソッドを使用し、ボクシングを忘れる。

1

私は実際にはこのアプローチが嫌いですが、IEnumerable<object>へのキャストを強制することなく、IEnumerableインターフェイスで直接呼び出し可能なLINQ-to-Objectsに似たツールセットを提供することは可能です。 )とキャストしないでIEnumerable<TFoo>(さらに悪い:私たちはTFooを知って書く必要があるだろう!)。

しかし、それは次のとおりです。ランタイムの

  • 無料ではありません:それは重いかもしれ、私は開発者のための
  • 自由でないもパフォーマンス・テストを実行しませんでした:あなたが実際にこれらすべてのLINQを記述する必要がありますIEnumerableの拡張メソッド(またはそれを行うライブラリを見つける)
  • 単純ではありません:入力するタイプを慎重に検査し、多くの可能なオプションに注意する必要があります
  • はオラクルではありません。実装するIEnumerableを実装していませんが、を実装していない場合は、エラーをスローするか、暗黙的にキャストしてIEnumerable<object>
  • のいずれかになります。IEnumerable<int>IEnumerable<string>の両方を実装するコレクションでは、でもここで.Net4 +のための例です諦めと右ここ

鳴らないIEnumerable<object>にキャスト:

using System; 
using System.Linq; 
using System.Collections.Generic; 

class Program 
{ 
    public static void Main() 
    { 
     Console.WriteLine("List<int>"); 
     new List<int> { 1, 2, 3 } 
      .DoSomething() 
      .DoSomething(); 

     Console.WriteLine("List<string>"); 
     new List<string> { "a", "b", "c" } 
      .DoSomething() 
      .DoSomething(); 

     Console.WriteLine("int[]"); 
     new int[] { 1, 2, 3 } 
      .DoSomething() 
      .DoSomething(); 

     Console.WriteLine("string[]"); 
     new string[] { "a", "b", "c" } 
      .DoSomething() 
      .DoSomething(); 

     Console.WriteLine("nongeneric collection with ints"); 
     var stack = new System.Collections.Stack(); 
     stack.Push(1); 
     stack.Push(2); 
     stack.Push(3); 
     stack 
      .DoSomething() 
      .DoSomething(); 

     Console.WriteLine("nongeneric collection with mixed items"); 
     new System.Collections.ArrayList { 1, "a", null } 
      .DoSomething() 
      .DoSomething(); 

     Console.WriteLine("nongeneric collection with .. bits"); 
     new System.Collections.BitArray(0x6D) 
      .DoSomething() 
      .DoSomething(); 
    } 
} 

public static class MyGenericUtils 
{ 
    public static System.Collections.IEnumerable DoSomething(this System.Collections.IEnumerable items) 
    { 
     // check the REAL type of incoming collection 
     // if it implements IEnumerable<T>, we're lucky! 
     // we can unwrap it 
     // ...usually. How to unwrap it if it implements it multiple times?! 
     var ietype = items.GetType().FindInterfaces((t, args) => 
      t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>), 
      null).SingleOrDefault(); 

     if (ietype != null) 
     { 
      return 
       doSomething_X(
        doSomething_X((dynamic)items) 
       ); 
       // .doSomething_X() - and since the compile-time type is 'dynamic' I cannot chain 
       // .doSomething_X() - it in normal way (despite the fact it would actually compile well) 
       // `dynamic` doesn't resolve extension methods! 
       // it would look for doSomething_X inside the returned object 
       // ..but that's just INTERNAL implementation. For the user 
       // on the outside it's chainable 
     } 
     else 
      // uh-oh. no what? it can be array, it can be a non-generic collection 
      // like System.Collections.Hashtable .. but.. 
      // from the type-definition point of view it means it holds any 
      // OBJECTs inside, even mixed types, and it exposes them as IEnumerable 
      // which returns them as OBJECTs, so.. 
      return items.Cast<object>() 
       .doSomething_X() 
       .doSomething_X(); 
    } 

    private static IEnumerable<T> doSomething_X<T>(this IEnumerable<T> valitems) 
    { 
     // do-whatever,let's just see it being called 
     Console.WriteLine("I got <{1}>: {0}", valitems.Count(), typeof(T)); 
     return valitems; 
    } 
} 

はい、それは愚かです。私はちょうどタイプ情報がであることを示すためにそれらを4回(2outsidex2inside)回チェーンしました。その後の呼び出しではが失われませんでした。要点は、「エントリーポイント」が非同義語IEnumerableをとり、<T>は可能な限り解決されることを示していました。コードを簡単に変更して、通常のLINQ-to-Objects .Count()メソッドにすることができます。同様に、他のすべての操作も書き込むことができます。

この例では、可能であればプラットフォームがIEnumerableの最も狭いTを解決するためにdynamicを使用しています(最初に確保する必要があります)。 dynamicなし(つまり、.Net2。0)私たちはリフレクションを通してdosomething_Xを呼び出すか、dosomething_refs<T>():where T:class + dosomething_vals<T>():where T:structという2度実装し、実際にキャストせずに正しく呼び出すためにいくつかの魔法を実行する必要があります。

それにもかかわらず、あなたは、非永続的なIEnumerableの背後に隠れているものに「直接」作用する何かのようなlinqを得ることができるようです。 IEnumerableの背後に隠れているオブジェクトには、完全な型の情報が残っているため(COMまたはRemotingではその前提が失敗する可能性があります)という事実に感謝します。しかし、私はIEnumerable<T>の解決がより良い選択だと思います。実際に他のオプションがない特別な場合には、普通の古いIEnumerableを残しましょう。

..oh ...と私上記のコードが正しい場合は、実際に、高速で、安全で、資源節約型を調査していなかった、怠惰-評価など

+0

うわー〜私は確かに驚いています。 (@ _ @) – Lingxi

関連する問題