web-dev-qa-db-ja.com

文字列がintに解析可能な場合、parse intを選択します

だから私はIEnumerable<string>は、intとして解析できる値と、そうでない値を含むことができます。

あなたが知っているように、 Int32.Parseは、文字列をintに変更できない場合に例外をスローし、Int32.TryParseを使用して、例外を処理せずに変換が可能かどうかを確認できます。

そのため、LINQクエリを使用して、途中で例外をスローせずにintとして解析できる文字列をワンライナーで解析したいと思います。私は解決策を持っていますが、これが最善のアプローチであるかどうかについてのコミュニティからのアドバイスをお願いします。

私が持っているものは次のとおりです。

int asInt = 0;
var ints = from str in strings
           where Int32.TryParse(str, out asInt)
           select Int32.Parse(str);

ご覧のとおり、asIntが成功する(boolを返す)かどうかを判断するために、TryParseを呼び出すためのスクラッチスペースとしてTryParseを使用しています。次に、投影では、実際に解析を実行しています。それはい感じです。

これは、LINQを使用して1行で解析可能な値をフィルター処理する最良の方法ですか?

48
Ben Lakey

クエリ構文でそれを行うのは困難ですが、ラムダ構文ではそれほど悪くはありません。

var ints = strings.Select(str => {
                             int value;
                             bool success = int.TryParse(str, out value);
                             return new { value, success };
                         })
                  .Where(pair => pair.success)
                  .Select(pair => pair.value);

あるいは、int?を返すメソッドを作成する価値があるかもしれません。

public static int? NullableTryParseInt32(string text)
{
    int value;
    return int.TryParse(text, out value) ? (int?) value : null;
}

次に、使用することができます:

var ints = from str in strings
           let nullable = NullableTryParseInt32(str)
           where nullable != null
           select nullable.Value;
79
Jon Skeet

まだ2つのコードラインですが、元のコードを少し短くすることができます。

int asInt = 0;
var ints = from str in strings
           where Int32.TryParse(str, out asInt)
           select asInt;

TryParseは選択時に既に実行されているため、asInt変数に値が設定されているので、それを戻り値として使用できます。再度解析する必要はありません。

13
Joe Enos

あなたが同僚が駐車場であなたをジャンプさせることを気にしないなら、linqの1つの真の行でこれを行う方法があります(セミコロンなし)....

strings.Select<string, Func<int,int?>>(s => (n) => int.TryParse(s, out n) ? (int?)n : (int?)null ).Where(λ => λ(0) != null).Select(λ => λ(0).Value);

実用的ではありませんが、1つのステートメントでこれを行うことは、あまりにも興味深い挑戦であり、見逃すことはできません。

6
Kelly Gendron

私はおそらくこの小さなユーティリティメソッドをどこかに持っているでしょう(私は実際に現在のコードベースで行います:-))

public static class SafeConvert
{
    public static int? ToInt32(string value) 
    {
        int n;
        if (!Int32.TryParse(value, out n))
            return null;
        return n;
    }
}

次に、このはるかにクリーンなLINQステートメントを使用します。

from str in strings
let number = SafeConvert.ToInt32(str)
where number != null
select number.Value;
4
driis

これを行うための拡張メソッドを定義する場合は、各Try関数に対して新しいnull-on-failureラッパーを記述する必要はなく、使いやすい一般的なソリューションを作成しますandでは、null値を除外する必要があります。

public delegate bool TryFunc<in TSource, TResult>(TSource arg, out TResult result);

public static IEnumerable<TResult> SelectTry<TSource, TResult>(this IEnumerable<TSource> source, TryFunc<TSource, TResult> selector)
{
    foreach(var s in source) {
        TResult r;
        if (selector(s, out r))
            yield return r;
    }
}

使用法:

var ints = strings.SelectTry<string, int>(int.TryParse);

C#がSelectTryのジェネリック型引数を推測できないのは少し厄介です。

TryFuncのTResultは共変にできません(つまり、out TResultFunc のように。 Eric Lippertとして 説明 出力パラメータは 実際には単にrefパラメータ 読み取り前書き込みルールを使用しています。

2
Carl Walsh

Carl Walshの答えに触発されて、私はそれをさらに一歩進めて、プロパティの解析を可能にしました:

public static IEnumerable<TResult> SelectTry<TSource, TValue, TResult>(
     this IEnumerable<TSource> source, 
     Func<TSource, TValue> selector, 
     TryFunc<TValue, TResult> executor)
{
    foreach (TSource s in source)
    {
        TResult r;
        if (executor(selector(s), out r))
            yield return r;
    }
}

これも fiddle にある例です。

public class Program
{
    public static void Main()
    {       
        IEnumerable<MyClass> myClassItems = new List<MyClass>() {new MyClass("1"), new MyClass("2"), new MyClass("InvalidValue"), new MyClass("3")};

        foreach (int integer in myClassItems.SelectTry<MyClass, string, int>(x => x.MyIntegerAsString, int.TryParse))
        {
            Console.WriteLine(integer);
        }
    }
}

public static class LinqUtilities
{
    public delegate bool TryFunc<in TSource, TResult>(TSource arg, out TResult result);

    public static IEnumerable<TResult> SelectTry<TSource, TValue, TResult>(
        this IEnumerable<TSource> source, 
        Func<TSource, TValue> selector, 
        TryFunc<TValue, TResult> executor)
    {
        foreach (TSource s in source)
        {
            TResult r;
            if (executor(selector(s), out r))
                yield return r;
        }
    }
}

public class MyClass
{
    public MyClass(string integerAsString)
    {
        this.MyIntegerAsString = integerAsString;
    }

     public string MyIntegerAsString{get;set;}
}

このプログラムの出力:

1

2

3

1
hbulens

追加の変数を使用するとusingいに感じることに同意します。

Jonの回答 およびC#7.0ソリューションへの更新に基づいて、新しい var out機能 :(それほど短くはありませんが、内部スコープやクエリ外の一時変数は必要ありません

var result = strings.Select(s => new { Success = int.TryParse(s, out var value), value })
                    .Where(pair => pair.Success)
                    .Select(pair => pair.value);

名前付きタプルと一緒に:

var result = strings.Select(s => (int.TryParse(s, out var value), value))
                    .Where(pair => pair.Item1)
                    .Select(pair => pair.value);

または、クエリ構文で使用するためのメソッドを提案する場合:

public static int? NullableTryParseInt32(string text)
{
    return int.TryParse(text, out var value) ? (int?)value : null;
}

また、追加のメソッドを使用せずにクエリ構文を提案したいのですが、次のリンクout varはc#7.0ではサポートされておらず、コンパイルエラーが発生します。

クエリ句内では、out変数とpattern変数の宣言は許可されません

リンク: クエリ式の式変数


これにより、C#7.0の機能により、以前の.NETバージョンで動作させることができます。

0
Gilad Green

1行のLinq式を探していて、すべてのループに新しいオブジェクトを割り当てることに問題がない場合は、より強力な SelectMany を使用して1つのLinq呼び出しでこれを行います

var ints = strings.SelectMany(str => {
    int value;
    if (int.TryParse(str, out value))
        return new int[] { value };
    return new int[] { };
});
0
Carl Walsh