La réponse sous la ligne a été écrite en 2008.
C # 7 a introduit la correspondance de motifs, qui a largement remplacé l' as
opérateur, comme vous pouvez maintenant l'écrire:
if (randomObject is TargetType tt)
{
// Use tt here
}
Notez que tt
c'est toujours dans la portée après cela, mais pas définitivement attribué. (Il est définitivement attribué dans le if
corps.) C'est légèrement ennuyeux dans certains cas, donc si vous vous souciez vraiment d'introduire le plus petit nombre de variables possible dans chaque étendue, vous voudrez peut-être toujours utiliser is
suivi d'un cast.
Je ne pense pas qu'aucune des réponses à ce jour (au moment de commencer cette réponse!) Ait vraiment expliqué où cela valait la peine d'être utilisé.
Ne faites pas ça:
// Bad code - checks type twice for no reason
if (randomObject is TargetType)
{
TargetType foo = (TargetType) randomObject;
// Do something with foo
}
Non seulement cette vérification est effectuée deux fois, mais elle peut également vérifier différentes choses, s'il randomObject
s'agit d'un champ plutôt que d'une variable locale. Il est possible que le «si» passe mais que la conversion échoue, si un autre thread change la valeur randomObject
entre les deux.
Si randomObject
vraiment devrait être une instance de TargetType
, c'est-à-dire si ce n'est pas le cas, cela signifie qu'il y a un bug, alors le casting est la bonne solution. Cela lève immédiatement une exception, ce qui signifie qu'aucun travail supplémentaire n'est effectué sous des hypothèses incorrectes, et l'exception affiche correctement le type de bogue.
// This will throw an exception if randomObject is non-null and
// refers to an object of an incompatible type. The cast is
// the best code if that's the behaviour you want.
TargetType convertedRandomObject = (TargetType) randomObject;
Si randomObject
peut être une instance de TargetType
et TargetType
est un type de référence, utilisez un code comme celui-ci:
TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject
}
Si randomObject
peut être une instance de TargetType
et TargetType
est un type valeur, nous ne pouvons pas l'utiliser as
avec TargetType
lui-même, mais nous pouvons utiliser un type nullable:
TargetType? convertedRandomObject = randomObject as TargetType?;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject.Value
}
(Remarque: actuellement, c'est en fait plus lent que + cast . Je pense que c'est plus élégant et cohérent, mais c'est parti.)
Si vous n'avez vraiment pas besoin de la valeur convertie, mais que vous avez juste besoin de savoir s'il s'agit d' une instance de TargetType, alors l' is
opérateur est votre ami. Dans ce cas, peu importe que TargetType soit un type de référence ou un type de valeur.
Il peut y avoir d'autres cas impliquant des génériques où cela is
est utile (parce que vous ne savez pas si T est un type de référence ou non, vous ne pouvez donc pas l'utiliser comme) mais ils sont relativement obscurs.
Je l'ai presque certainement utilisé is
pour le cas de type valeur avant maintenant, sans avoir pensé à utiliser un type nullable et as
ensemble :)
EDIT: Notez qu'aucun des éléments ci-dessus ne parle de performances, à l'exception du cas du type de valeur, où j'ai remarqué que la décompression vers un type de valeur nullable est en fait plus lente - mais cohérente.
Selon la réponse de naasking, is-and-cast ou is-and-as sont à la fois aussi rapides que la vérification as-and-null avec les JIT modernes, comme le montre le code ci-dessous:
using System;
using System.Diagnostics;
using System.Linq;
class Test
{
const int Size = 30000000;
static void Main()
{
object[] values = new object[Size];
for (int i = 0; i < Size - 2; i += 3)
{
values[i] = null;
values[i + 1] = "x";
values[i + 2] = new object();
}
FindLengthWithIsAndCast(values);
FindLengthWithIsAndAs(values);
FindLengthWithAsAndNullCheck(values);
}
static void FindLengthWithIsAndCast(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
if (o is string)
{
string a = (string) o;
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("Is and Cast: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
static void FindLengthWithIsAndAs(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
if (o is string)
{
string a = o as string;
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("Is and As: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
static void FindLengthWithAsAndNullCheck(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
string a = o as string;
if (a != null)
{
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("As and null check: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
}
Sur mon ordinateur portable, ils s'exécutent tous en 60 ms environ. Deux choses à noter:
- Il n'y a pas de différence significative entre eux. (En fait, il existe des situations dans lesquelles la vérification as-plus-null est certainement plus lente. Le code ci-dessus facilite en fait la vérification de type car il s'agit d'une classe scellée; si vous recherchez une interface, l'équilibre bascule légèrement en faveur de as-plus-null-check.)
- Ils sont tous incroyablement rapides. Ce ne sera tout simplement pas le goulot d'étranglement dans votre code, sauf si vous n'allez vraiment rien faire avec les valeurs par la suite.
Alors ne nous inquiétons pas des performances. Soucions-nous de l'exactitude et de la cohérence.
Je maintiens que is-and-cast (ou is-and-as) ne sont pas sûrs lorsqu'ils traitent avec des variables, car le type de la valeur à laquelle il se réfère peut changer en raison d'un autre thread entre le test et le cast. Ce serait une situation assez rare - mais je préfère avoir une convention que je peux utiliser de manière cohérente.
Je maintiens également que la vérification as-then-null donne une meilleure séparation des préoccupations. Nous avons une instruction qui tente une conversion, puis une instruction qui utilise le résultat. Le is-and-cast ou is-and-as effectue un test, puis une autre tentative de conversion de la valeur.
Autrement dit, quelqu'un pourrait-il jamais écrire:
int value;
if (int.TryParse(text, out value))
{
value = int.Parse(text);
// Use value
}
C'est en quelque sorte ce que fait-et-cast - bien que de manière évidemment moins chère.