web-dev-qa-db-fra.com

Sélectionnez analyse int, si chaîne est analysable en entier

J'ai donc un IEnumerable<string> qui peut contenir des valeurs qui peuvent être analysées comme int, ainsi que des valeurs qui ne peuvent pas l'être.

Comme vous le savez, Int32.Parse lève une exception si une chaîne ne peut pas être changée en un entier, alors que Int32.TryParse peut être utilisé pour vérifier si la conversion était possible sans traiter l'exception. 

Je souhaite donc utiliser une requête LINQ pour analyser en une seule ligne les chaînes qui peuvent être analysées en tant que int, sans générer d'exception. J'ai une solution, mais j'aimerais avoir l'avis de la communauté pour savoir si c'est la meilleure approche.

Voici ce que j'ai

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

Comme vous pouvez le constater, j'utilise asInt comme espace de travail pour l'appel à TryParse, afin de déterminer si TryParse réussira (return bool). Ensuite, dans la projection, je suis en train d'effectuer l'analyse. C'est moche.

Est-ce le meilleur moyen de filtrer les valeurs analysables sur une ligne à l'aide de LINQ?

45
Ben Lakey

C'est difficile à faire avec la syntaxe de requête, mais ce n'est pas si mal avec la syntaxe lambda:

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);

Vous pouvez également trouver intéressant d'écrire une méthode qui retourne un int?:

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

Ensuite, vous pouvez simplement utiliser:

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

Il reste encore deux lignes de code, mais vous pouvez raccourcir un peu l’original:

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

Comme TryParse est déjà exécuté au moment de la sélection, la variable asInt est renseignée. Vous pouvez donc l'utiliser comme valeur de retour. Vous n'avez pas besoin de l'analyser à nouveau.

12
Joe Enos

Si cela ne vous dérange pas que vos collègues vous sautent sur le parking, vous pouvez le faire en une seule ligne de linq (sans point-virgule) ....

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

Ce n'est pas pratique, mais le faire en une seule déclaration était un défi beaucoup trop intéressant à relever. 

6
Kelly Gendron

J'aurais probablement cette petite méthode utilitaire quelque part (ce que je fais dans mon code actuel :-)) 

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

Ensuite, vous utilisez cette instruction LINQ beaucoup plus propre:

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

Si vous souhaitez définir une méthode d'extension à cette fin, je créerais une solution générale simple à utiliser, au lieu de vous demander d'écrire un nouveau wrapper null-on-failure pour chaque fonction Try, et requiert: vous filtrer les valeurs nulles.

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;
    }
}

Usage:

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

C’est un peu gênant que C # ne puisse pas déduire les arguments de type générique de SelectTry.

(Le TResult de TryFunc ne peut pas être covariant (c.-à-d. out TResult) comme Func . Comme Eric Lippert, les paramètres explique out sont en fait, il ne s'agit que de paramètres avec une écriture fantaisiste avant lire les règles.)

2
Carl Walsh

Ce serait LINQ-to-objets:

static int? ParseInt32(string s) {
    int i;
    if(int.TryParse(s,out i)) return i;
    return null;
}

Puis dans la requête:

let i = ParseInt32(str)
where i != null
select i.Value;
2
Marc Gravell

Inspiré par la réponse de Carl Walsh, je suis allé un peu plus loin pour permettre l'analyse syntaxique des propriétés:

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;
    }
}

Voici un exemple qui peut également être trouvé dans ce violon :

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;}
}

Résultat de ce programme:

3

1
hbulens

Je conviens que l’utilisation de la variable supplémentaire moche} _.

Sur la base de les réponses de Jon et mettant à jour leurs solutions C # 7.0, vous pouvez utiliser la nouvelle fonctionnalité var out : (pas beaucoup plus court mais pas besoin d'une portée interne ou de variables temp)

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

et avec des tuples nommés:

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

Ou si vous suggérez une méthode à utiliser dans la syntaxe de requête:

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

J'aimerais aussi suggérer une syntaxe de requête sans méthode supplémentaire, mais comme indiqué dans le lien suivant, out var n'est pas pris en charge par c # 7.0 et entraîne l'erreur de compilation:

Les déclarations de variable de modèle et de modèle ne sont pas autorisées dans une clause de requête

Le lien: Variables d'expression dans les expressions de requête


Il s’agit d’une fonctionnalité C # 7.0 que l’on peut faire fonctionner sur des versions antérieures de .NET:

0
Gilad Green

Si vous recherchez une expression Linq sur une ligne et que vous pouvez attribuer un nouvel objet à chaque boucle, j'utiliserais le plus puissant SelectMany pour le faire avec un appel Linq.

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