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?
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;
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.
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.
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;
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.)
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;
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:
1
2
3
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:
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[] { };
});