A resposta abaixo da linha foi escrita em 2008.
O C # 7 introduziu a correspondência de padrões, que substituiu amplamente o as
operador, como agora você pode escrever:
if (randomObject is TargetType tt)
{
// Use tt here
}
Observe que tt
ainda está no escopo depois disso, mas não está definitivamente atribuído. ( Definitivamente, é atribuído dentro do if
corpo.) Isso é um pouco irritante em alguns casos, por isso, se você realmente se importa em introduzir o menor número possível de variáveis em todos os escopos, você ainda pode querer usar is
seguido de uma conversão.
Eu não acho que nenhuma das respostas até agora (no momento de iniciar essa resposta!) Tenha realmente explicado onde vale a pena usar quais.
Não faça isso:
// Bad code - checks type twice for no reason
if (randomObject is TargetType)
{
TargetType foo = (TargetType) randomObject;
// Do something with foo
}
Essa verificação não é apenas duas vezes, mas também pode estar verificando coisas diferentes, se randomObject
for um campo e não uma variável local. É possível que o "se" passe, mas o elenco falhe, se outro encadeamento alterar o valor randomObject
entre os dois.
Se randomObject
realmente deve ser uma instância de TargetType
, ou seja, se não for, isso significa que há um erro, então a conversão é a solução certa. Isso gera uma exceção imediatamente, o que significa que não há mais trabalho sob suposições incorretas e a exceção mostra corretamente o tipo de bug.
// 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;
Se randomObject
pode ser uma instância TargetType
e TargetType
é um tipo de referência, use um código como este:
TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject
}
Se randomObject
pode ser uma instância TargetType
e TargetType
é um tipo de valor, não podemos usar as
com TargetType
ele mesmo, mas podemos usar um tipo que permite valor nulo:
TargetType? convertedRandomObject = randomObject as TargetType?;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject.Value
}
(Nota: atualmente, na verdade, isso é mais lento que o + elenco . Acho que é mais elegante e consistente, mas lá vamos nós.)
Se você realmente não precisa do valor convertido, mas precisa saber se é uma instância do TargetType, o is
operador é seu amigo. Nesse caso, não importa se TargetType é um tipo de referência ou um valor.
Pode haver outros casos envolvendo genéricos onde is
é útil (porque você pode não saber se T é um tipo de referência ou não, então não pode usá-lo como), mas eles são relativamente obscuros.
Eu quase certamente usei is
para o caso do tipo de valor antes, agora, sem ter pensado em usar um tipo anulável e as
juntos :)
EDIT: Observe que nenhuma das opções acima fala sobre desempenho, exceto o caso do tipo de valor, em que observei que a descompactação para um tipo de valor nulo é realmente mais lenta - mas consistente.
De acordo com a resposta de naasking, o is-and-cast ou is-as-são tão rápidos quanto a verificação de nulos e com JITs modernos, como mostra o código abaixo:
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);
}
}
No meu laptop, tudo isso é executado em cerca de 60ms. Duas coisas a serem observadas:
- Não há diferença significativa entre eles. (De fato, há situações em que a verificação como mais mais nula definitivamente é mais lenta. O código acima realmente facilita o tipo de verificação porque é para uma classe selada; se você estiver procurando por uma interface, o saldo será ligeiramente mais baixo. em favor do cheque as-plus-null.)
- Eles são todos incrivelmente rápidos. Isso simplesmente não será o gargalo do seu código, a menos que você realmente não faça nada com os valores posteriormente.
Então não vamos nos preocupar com o desempenho. Vamos nos preocupar com correção e consistência.
Eu mantenho que o is-and-cast (ou é-e-as) é inseguro ao lidar com variáveis, pois o tipo de valor a que ele se refere pode mudar devido a outro encadeamento entre o teste e o cast. Essa seria uma situação bastante rara - mas prefiro ter uma convenção que possa usar de forma consistente.
Também mantenho que a verificação como então nula oferece uma melhor separação de preocupações. Temos uma declaração que tenta uma conversão e, em seguida, uma declaração que usa o resultado. O is-and-cast ou is-and-as executa um teste e, em seguida, outra tentativa de converter o valor.
Para colocar de outra forma, alguém sempre escreve:
int value;
if (int.TryParse(text, out value))
{
value = int.Parse(text);
// Use value
}
Isso é o que o elenco está fazendo - embora obviamente de uma maneira um pouco mais barata.