LINQでリストが空かどうかを確認する


122

リストが空かどうかを判断するための「速度」と読みやすさの両方を考慮した「最良の」方法は何ですか?リストがタイプIEnumerable<T>で、Countプロパティがない場合でも。

今、私はこれの間で投げ合っています:

if (myList.Count() == 0) { ... }

この:

if (!myList.Any()) { ... }

2番目のオプション(IEnumerableの場合)がすべてのアイテムにアクセスしてカウントを返す必要があるのに対し、2番目のオプションは最初のアイテムが見つかるとすぐに結果が返されるため、より高速であると思います。

そうは言っても、2番目のオプションはあなたにとって読みやすいように見えますか?どっちがいい?または、空のリストをテストするためのより良い方法を考えられますか?

Edit @lassevkの応答は、最も論理的であるようであり、可能な場合はキャッシュされたカウントを使用するための実行時チェックのビットと相まって、次のようになります。

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list is ICollection<T>) return ((ICollection<T>)list).Count == 0;

    return !list.Any();
}

5
はるかに良く混ぜていないiscastが、使用asしてnull確認しますICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev

2
なぜ追加のメソッドを書くのですか?はlist.Any()同等ではないlist.IsEmptyですか?フレームワークメソッドは最適化する必要があります。パフォーマンスのボトルネックであることがわかった場合にのみ、新しいフレームワークメソッドを作成する価値があります。
dbkk

6
提案された実装のパフォーマンスを測定することに迷惑をかけた人はいますか、それともアイデアを捨てているだけですか?
マイケルブラウン

IsEmpty拡張メソッドを追加する.NET Coreクラスライブラリに問題を提案しました。 github.com/dotnet/corefx/issues/35054 確認して投票してください。
室星良太

回答:


100

あなたはこれを行うことができます:

public static Boolean IsEmpty<T>(this IEnumerable<T> source)
{
    if (source == null)
        return true; // or throw an exception
    return !source.Any();
}

編集:基になるソースに実際に高速なCountプロパティがある場合、単に.Countメソッドを使用すると高速になることに注意してください。上記の有効な最適化は、いくつかの基本タイプを検出し、.Any()アプローチの代わりにそれらの.Countプロパティを使用することですが、保証ができない場合は.Any()にフォールバックします。


4
または、1行を使用してreturn(source == null)を実行しますか?true:!source.Any(); (例外をスローしない場合)
ゲージ

1
はい、nullの例外をスローしますが、と呼ばれる2番目の拡張メソッドを追加しIsNullOrEmpty()ます。
devuxer 2011

1
public static Boolean IsNullOrEmpty <T>(this IEnumerable <T> source){return source == null || !source.Any(); }
dan

1
@Gage Nowadays:return !source?.Any() ?? true;
ricksmt

@ricksmt更新ありがとうございます!私は間違いなくそれを使用します!
ゲージ

14

私はあなたが解決したと思われるコードに少し追加します:もチェックします。ICollectionこれは、廃止されていない総称クラス(つまり、Queue<T>およびStack<T>)でも実装されているためです。より慣用的であり、より高速であることが示されているので、as代わりにも使用しますis

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list == null)
    {
        throw new ArgumentNullException("list");
    }

    var genericCollection = list as ICollection<T>;
    if (genericCollection != null)
    {
        return genericCollection.Count == 0;
    }

    var nonGenericCollection = list as ICollection;
    if (nonGenericCollection != null)
    {
        return nonGenericCollection.Count == 0;
    }

    return !list.Any();
}

1
私はこの答えが好きです。警告の1言は、NotSupportedExceptionやなどのインターフェースを完全に実装していない場合、一部のコレクションは例外をスローするということですNotImplementedException。私が最初に使用したコレクションを見つけたとき、私は最初にあなたのコード例を使用しました(カウントは知っていました...)。
サム

1
このような最適化が、すべての要素を列挙する必要があるCount()などのメソッドに役立つ理由を理解しています。しかし、Any()は最大で1つの要素を列挙するだけでよいので、ここでポイントを確認できません。一方、追加するキャストとifステートメントは、すべての呼び出しで支払う必要がある固定コストです。
codymanix 2018年

8

LINQ自体は、なんとかしてCount()メソッドを真剣に最適化している必要があります。

これはあなたを驚かせますか?IList実装でCountは、要素の数を直接読み取るだけでAnyIEnumerable.GetEnumeratorメソッドをクエリし、インスタンスを作成して呼び出す必要があると思いますMoveNextして少なくとも1回は。

/マット@EDIT:

IEnumerableのCount()拡張メソッドが次のようなことをしているとしか想定できません。

はい、もちろんそうです。これが私が言ったことです。実際には、ICollection代わりに使用しますIListが、結果は同じです。


6

私は簡単なテストを書きました、これを試してください:

 IEnumerable<Object> myList = new List<Object>();

 Stopwatch watch = new Stopwatch();

 int x;

 watch.Start();
 for (var i = 0; i <= 1000000; i++)
 {
    if (myList.Count() == 0) x = i; 
 }
 watch.Stop();

 Stopwatch watch2 = new Stopwatch();

 watch2.Start();
 for (var i = 0; i <= 1000000; i++)
 {
     if (!myList.Any()) x = i;
 }
 watch2.Stop();

 Console.WriteLine("myList.Count() = " + watch.ElapsedMilliseconds.ToString());
 Console.WriteLine("myList.Any() = " + watch2.ElapsedMilliseconds.ToString());
 Console.ReadLine();

2番目はほぼ3倍遅くなります:)

スタックや配列、または他のシナリオでストップウォッチテストを再試行することは、実際にはリストの種類によって異なります。カウントが遅いことが証明されているためです。

だから、それはあなたが使っているリストのタイプに依存すると思います!

(ちょうど指摘しておきますが、私は2000以上のオブジェクトをリストに入れましたが、他のタイプとは逆に、カウントはさらに速くなりました)


12
Enumerable.Count<T>()の特別な処理がありICollection<T>ます。あなたが何かでこれをしようとした場合、他の基本的なリストよりも、私はあなたが表示されます期待して大幅に異なる(遅い)の結果を。Any()ただし、ほぼ同じままです。
Marc Gravell

2
私はマークに同意する必要があります。これは本当に公正なテストではありません。
Dan Tao

for Enumerable.Any<T>()に特別な処理がない理由はICollection<T>何ですか?確かに、パラメータAny()レスはCountプロパティICollection<T>もチェックするだけですか?
ルカゾイド

5

List.CountMicrosoftのドキュメントによるとO(1)です:http :
//msdn.microsoft.com/en-us/library/27b47ht3.aspx

だから使うだけ List.Count == 0するクエリよりもはるかに高速です

これは、Countと呼ばれるデータメンバーがあり、リストに何かが追加または削除されるたびに更新されるため、呼び出すときにList.Countすべての要素を反復処理して取得する必要がなく、データメンバーを返すだけです。


1
「IEnumerable」の場合は、いいえ。(初心者のために、IEnumerableには "Count"プロパティがなく、Count()メソッドがあります。)「Count()」を呼び出すには、IEnumerableがリスト内のすべての要素を調べる必要があります。一方、「Any」は1つの要素が見つかるとすぐに戻ります。
2016

データソースによって異なります。収量を使用してIEnumerableを構築する場合、IEnumerableをトラバースしてサイズを確認する必要があります。したがって、場合によってはO(1)のみです。常にO(1)とは限りません。
TamusJRoyce 2017

3

複数のアイテムがある場合、2番目のオプションははるかに高速です。

  • Any() 1つのアイテムが見つかるとすぐに戻ります。
  • Count() リスト全体を調べ続ける必要があります。

たとえば、列挙に1000の項目があったとします。

  • Any() 最初のものをチェックしてから、trueを返します。
  • Count() 列挙全体をトラバースした後、1000を返します。

述語オーバーライドの1つを使用する場合、これはさらに悪い可能性があります。一致が1つしかない場合でも、Count()はすべての項目をチェックする必要があります。

Any oneの使用に慣れます-意味があり、読みやすいです。

1つの注意点-IEnumerableだけでなくリストがある場合は、そのリストのCountプロパティを使用してください。


Any()とCount()の違いは明らかのようですが、@ crucibleのプロファイリングコードは、IEnumerable <T>の特定の実装ではCount()の方が高速であることを示しているようです。List <T>の場合、リストのサイズが数千項目に達するまで、Any()でCount()よりも速い結果を得ることができません。LINQ自体は、なんとかしてCount()メソッドに関していくつかの深刻な最適化を行っている必要があります。
マットハミルトン

3

@Konradが驚いたのは、私のテストではIEnumerable<T>、を受け入れるメソッドにリストを渡しているため、ランタイムがのCount()拡張メソッドを呼び出してリストを最適化できないことですIList<T>

IEnumerableのCount()拡張メソッドが次のようなことをしているとしか想定できません。

public static int Count<T>(this IEnumerable<T> list)
{
    if (list is IList<T>) return ((IList<T>)list).Count;

    int i = 0;
    foreach (var t in list) i++;
    return i;
}

...言い換えれば、 IList<T>

/ EDIT @Konrad +1 mate-オンになっている可能性が高いICollection<T>です。


1

では、これはどうですか?

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    return !enumerable.GetEnumerator().MoveNext();
}

編集:私は誰かがこのソリューションをすでにスケッチしていることに気づきました。Any()メソッドがこれを行うと述べられましたが、自分でやってみませんか?よろしく


3
しかしusingIDisposableオブジェクトをブロックで適切に囲むと、オブジェクトが構築されて放棄されてしまうため、簡潔さは低下します。そして、もちろん、すでに存在する拡張メソッドを利用して、それを(これを正確に行う)に変更すると、より簡潔になりますreturn !enumerable.Any()
Dan Tao

なぜ既存のメソッドを書き換えるのですか?前述のとおりAny()、これは正確に実行されるため、まったく同じメソッドを別の名前で追加すると混乱を招きます。
Julien N

1

別のアイデア:

if(enumerable.FirstOrDefault() != null)

ただし、Any()のアプローチの方が好きです。


3
最初の要素がnullである空でないリストがある場合はどうなりますか?
Ekevoo

1

これをEntity Frameworkで機能させるには、これが不可欠でした。

var genericCollection = list as ICollection<T>;

if (genericCollection != null)
{
   //your code 
}

それはどのように質問に答えますか?内部に要素がない場合でも、コレクションをnullにすることはできません。
Martin Verjans 2017

0

Count()で確認すると、Linqはデータベースで「SELECT COUNT(*)..」を実行しますが、結果にデータが含まれているかどうかを確認する必要がある場合は、Count()ではなくFirstOrDefault()を導入することにしました。

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

if (cfop.Count() > 0)
{
    var itemCfop = cfop.First();
    //....
}

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

var itemCfop = cfop.FirstOrDefault();

if (itemCfop != null)
{
    //....
}

0
private bool NullTest<T>(T[] list, string attribute)

    {
        bool status = false;
        if (list != null)
        {
            int flag = 0;
            var property = GetProperty(list.FirstOrDefault(), attribute);
            foreach (T obj in list)
            {
                if (property.GetValue(obj, null) == null)
                    flag++;
            }
            status = flag == 0 ? true : false;
        }
        return status;
    }


public PropertyInfo GetProperty<T>(T obj, string str)

    {
        Expression<Func<T, string, PropertyInfo>> GetProperty = (TypeObj, Column) => TypeObj.GetType().GetProperty(TypeObj
            .GetType().GetProperties().ToList()
            .Find(property => property.Name
            .ToLower() == Column
            .ToLower()).Name.ToString());
        return GetProperty.Compile()(obj, str);
    }

0

述語を可能にするDan Taoの回答の私の実装は次のとおりです。

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any(predicate);
}

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any();
}

private static bool IsCollectionAndEmpty<TSource>(IEnumerable<TSource> source)
{
    var genericCollection = source as ICollection<TSource>;
    if (genericCollection != null) return genericCollection.Count == 0;
    var nonGenericCollection = source as ICollection;
    if (nonGenericCollection != null) return nonGenericCollection.Count == 0;
    return false;
}

-1
List<T> li = new List<T>();
(li.First().DefaultValue.HasValue) ? string.Format("{0:yyyy/MM/dd}", sender.First().DefaultValue.Value) : string.Empty;

-3

myList.ToList().Count == 0。それで全部です


1
これはひどい考えです。ToList()は、列挙型を完全に評価する必要があるため、使いすぎないでください。代わりに.Any()を使用してください。
Jon Rea

-5

この拡張方法は私にとってはうまくいきます:

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    try
    {
        enumerable.First();
        return false;
    }
    catch (InvalidOperationException)
    {
        return true;
    }
}

5
このような例外の使用は避けてください。上記のコードでは、明確に定義された特定の入力(つまり、空の列挙)の例外が予想されます。したがって、それらは例外ではなく、ルールです。これは、読みやすさとパフォーマンスに影響を与えるこの制御メカニズムの悪用です。本当に例外的な場合のために、例外の使用を予約してください。
Konrad Rudolph、

一般的に、私は同意します。しかし、これは対応する不足しているIsEmptyメソッドの回避策です。そして、私は回避策が何かを行うための理想的な方法では決してないと主張します...さらに、特にこの場合、意図は非常に明確で、「ダーティ」コードはカプセル化され、明確に定義された場所に隠されます。
ジョニーディー

3
-1:この方法で実行する場合は、ChulioMartinezの回答のように、FirstOrDefault()を使用します。
ダニエルローズ

3
例外処理のパフォーマンス効率は非常に低くなっています。したがって、これはここで最悪の解決策になる可能性があります。
Julien N

「例外は例外的でなければならない。」-通常のプログラムフローには使用しないでください。
Jon Rea
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.