Transmitindo vs usando a palavra-chave 'as' no CLR

387

Ao programar interfaces, descobri que estou fazendo muitas conversões de conversão ou de tipo de objeto.

Existe uma diferença entre esses dois métodos de conversão? Em caso afirmativo, existe uma diferença de custo ou como isso afeta meu programa?

public interface IMyInterface
{
    void AMethod();
}

public class MyClass : IMyInterface
{
    public void AMethod()
    {
       //Do work
    }

    // Other helper methods....
}

public class Implementation
{
    IMyInterface _MyObj;
    MyClass _myCls1;
    MyClass _myCls2;

    public Implementation()
    {
        _MyObj = new MyClass();

        // What is the difference here:
        _myCls1 = (MyClass)_MyObj;
        _myCls2 = (_MyObj as MyClass);
    }
}

Além disso, o que é "em geral" o método preferido?

Frank V
fonte
Você poderia adicionar um pequeno exemplo de por que está usando os modelos em primeiro lugar para a pergunta, ou talvez iniciar um novo? Estou meio interessado em saber por que você precisaria do elenco apenas para testes de unidade. Eu acho que está fora do escopo desta questão.
Erik van Brakel
2
Provavelmente, posso alterar meu teste de unidade para evitar essa necessidade. Basicamente, tudo se resume ao fato de eu ter uma propriedade no meu objeto concreto que não está na interface. Eu preciso definir essa propriedade, mas na vida real essa propriedade teria sido definida por outros meios. Isso responde à sua pergunta?
Frank V
Como Patrik Hägne aponta astutamente abaixo, não é uma diferença.
Neil

Respostas:

519

A resposta abaixo da linha foi escrita em 2008.

O C # 7 introduziu a correspondência de padrões, que substituiu amplamente o asoperador, como agora você pode escrever:

if (randomObject is TargetType tt)
{
    // Use tt here
}

Observe que ttainda está no escopo depois disso, mas não está definitivamente atribuído. ( Definitivamente, é atribuído dentro do ifcorpo.) 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 isseguido 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 randomObjectfor 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 randomObjectentre os dois.

  • Se randomObjectrealmente 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 TargetTypee 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 TargetTypee TargetTypeé um tipo de valor, não podemos usar ascom TargetTypeele 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 isoperador é 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 ispara o caso do tipo de valor antes, agora, sem ter pensado em usar um tipo anulável e asjuntos :)


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.

Jon Skeet
fonte
7
Aqui está o custo de is / as / casting em termos de IL: atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/…
plinth
3
No caso, se targetObject pode ser do tipo target, por que usar a combinação "is" e cast é considerada uma prática ruim? Quero dizer, ele gera um código mais lento, mas, neste caso, as intenções são mais claras do que a conversão do AS, como "Faça algo se targetObject for targetType", em vez de "Faça algo se targetObject for nulo", além disso, a cláusula AS criará uma variável desnecessária fora do escopo do FI.
Valera Kolupaev 27/05
2
@ Valera: Bons pontos, embora eu sugira que o teste as / null seja suficientemente idiomático para que a intenção seja clara para quase todos os desenvolvedores de C #. Eu não gosto da duplicação envolvida no elenco do is +, pessoalmente. Na verdade, eu gostaria de uma espécie de construção "como se", que executa as duas ações em uma. Eles andam juntos tantas vezes ...
Jon Skeet
2
É um jogo de tiro em primeira pessoa, com uma jogabilidade muito boa, mas com um pouco de dificuldade, o que torna o jogo muito mais divertido e divertido, além de ser uma ótima opção de jogo para quem gosta de jogabilidade. de 128.000.000 itens.
precisa
2
Com C # 7 você pode fazer: if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}ou uso switch/ case ver docs
WerWet
72

"as" retornará NULL se não for possível converter.

a transmissão antes criará uma exceção.

Para o desempenho, gerar uma exceção geralmente é mais caro no tempo.

Patrick Desjardins
fonte
3
Levantamento exceção é mais caro, mas se você sabe que o objeto pode ser convertido corretamente, como requer mais tempo por causa da verificação de segurança (veja a resposta de Anton). No entanto, acredito que o custo da verificação de segurança é bem pequeno.
17
O custo de potencialmente gerar uma exceção é um fator a ser considerado, mas geralmente é o design correto.
Jeffrey L Whitledge
@panesofglass - Para tipos de referência, a compatibilidade de conversão sempre será verificada no tempo de execução para as e cast, para que o fator não faça distinção entre as duas opções. (Se não fosse assim, então elenco não poderia gerar uma exceção.)
Jeffrey L Whitledge
4
@Frank - Se você precisar usar uma coleção pré-genérica, por exemplo, e um método em sua API exigir uma lista de Funcionários, e algum coringa passar uma lista de Produtos, uma exceção de conversão inválida pode ser apropriada para sinalizar a violação dos requisitos da interface.
Jeffrey L Whitledge
27

Aqui está outra resposta, com algumas comparações de IL. Considere a classe:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 


    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

Agora observe a IL que cada método produz. Mesmo se os códigos operacionais não significam nada para você, você pode ver uma grande diferença - isinst está sendo chamado seguido por castclass no método DirectCast. Então, duas chamadas em vez de uma basicamente.

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

A palavra-chave isinst versus a classe de elenco

Esta postagem no blog tem uma comparação decente entre as duas maneiras de fazer isso. Seu resumo é:

  • Em uma comparação direta, isinst é mais rápido que a classe de elenco (embora apenas um pouco)
  • Ao ter que executar verificações para garantir que a conversão foi bem-sucedida, o isinst foi significativamente mais rápido que o da classe
  • Uma combinação de isinst e castclass não deve ser usada, pois foi muito mais lenta que a conversão "segura" mais rápida (mais de 12% mais lenta)

Pessoalmente, sempre uso o As, porque é fácil de ler e é recomendado pela equipe de desenvolvimento .NET (ou mesmo Jeffrey Richter)

Chris S
fonte
Eu estava procurando uma explicação clara para o casting vs as, essa resposta torna muito mais clara, pois envolve uma explicação passo a passo da linguagem intermediária comum. Obrigado!
Morse
18

Uma das diferenças mais sutis entre as duas é que a palavra-chave "as" não pode ser usada para conversão quando um operador de conversão está envolvido:

public class Foo
{
    public string Value;

    public static explicit operator string(Foo f)
    {
        return f.Value;
    }

}

public class Example
{
    public void Convert()
    {
        var f = new Foo();
        f.Value = "abc";

        string cast = (string)f;
        string tryCast = f as string;
    }
}

Isso não será compilado (embora eu ache que ocorreu nas versões anteriores) na última linha, pois as palavras-chave "as" não levam em consideração os operadores de conversão. A linha string cast = (string)f;funciona muito bem.

Patrik Hägne
fonte
12

como nunca lança uma exceção se não puder executar a conversão retornando nulo ( como opera apenas em tipos de referência). Então, usar como é basicamente equivalente a

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;

As conversões no estilo C, por outro lado, lançam uma exceção quando nenhuma conversão é possível.

Anton Gogolev
fonte
4
Equivalente, sim, mas não é o mesmo. Isso gera muito mais código do que como.
plinth 30/01
10

Não é realmente uma resposta para sua pergunta, mas o que eu acho é um ponto relacionado importante.

Se você estiver programando para uma interface, não precisará converter. Esperamos que esses lançamentos sejam muito raros. Caso contrário, você provavelmente precisará repensar algumas de suas interfaces.

sapo
fonte
O elenco, até agora, foi principalmente necessário para meus testes de unidade, mas obrigado por trazê-lo à tona. Vou manter isso em mente enquanto trabalho nisso.
22830 Frank V
Concordo com o sapo, também estou curioso para saber por que o aspecto do teste de unidade é relevante para o elenco para você @Frank V. Onde há necessidade de elenco, muitas vezes há necessidade de reprojetar ou refatorar, pois sugere que você está tentando calçar diferentes problemas onde eles devem ser gerenciados de maneira diferente.
O senador
@ TheSenator Esta pergunta tem mais de 3 anos, então eu realmente não me lembro. Mas eu provavelmente estava usando agressivamente as interfaces, mesmo quando testava a unidade. Possivelmente porque eu estava usando o padrão de fábrica e não tinha acesso a um construtor público nos objetos de destino para testar.
Frank V
9

Por favor, ignore o conselho de Jon Skeet, re: evite o teste e teste de elenco, ou seja:

if (randomObject is TargetType)
{
    TargetType foo = randomObject as TargetType;
    // Do something with foo
}

A ideia de que isso custa mais do que um elenco e um teste nulo é um MITO :

TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
    // Do stuff with convertedRandomObject
}

É uma micro-otimização que não funciona. Fiz alguns testes reais e o teste-e-conversão é realmente mais rápido que a comparação de conversão-e-nulo, e também é mais seguro, porque você não tem a possibilidade de ter uma referência nula no escopo fora do if, caso o elenco falhou.

Se você deseja um motivo pelo qual o teste e a conversão são mais rápidos, ou pelo menos não mais lentos, existe um motivo simples e complexo.

Simples: até compiladores ingênuos unirão duas operações semelhantes, como test-and-cast, em um único teste e ramificação. teste de conversão e nulo pode forçar dois testes e uma ramificação, um para o teste de tipo e a conversão para nulo em caso de falha, um para a verificação nula. No mínimo, os dois otimizarão para um único teste e ramificação, para que o teste e a conversão não sejam mais lentos nem mais rápidos que o teste de conversão e nulo.

Complexo: por que o teste e a conversão são mais rápidos: o teste de conversão e nulo introduz outra variável no escopo externo, que o compilador deve rastrear quanto à disponibilidade, e talvez não seja possível otimizar essa variável, dependendo da complexidade do seu controle. fluxo é. Por outro lado, o teste e conversão introduz uma nova variável apenas em um escopo delimitado, para que o compilador saiba que a variável está inoperante após a saída do escopo e, assim, otimize melhor a alocação de registros.

Portanto, por favor, deixe que este conselho "teste de elenco e nulo seja melhor do que o teste e elenco" DIE. POR FAVOR. o teste e elenco é mais seguro e mais rápido.

naasking
fonte
7
@naasking: Se você testar duas vezes (conforme seu primeiro trecho), há uma chance de o tipo mudar entre os dois testes, se for um campo ou refparâmetro. É seguro para variáveis ​​locais, mas não para campos. Eu estaria interessado em executar seus benchmarks, mas o código que você forneceu em sua postagem no blog não está completo. Concordo em não otimizar a otimização, mas não acho que usar o valor duas vezes seja mais legível ou elegante do que usar "como" e um teste de nulidade. (Definitivamente, eu usaria um elenco reto, e não "como" depois de um é, btw).
Jon Skeet
5
Também não vejo por que é mais seguro. Eu mostrei porque é menos seguro, de fato. Claro, você acaba com uma variável no escopo que pode ser nula, mas, a menos que você comece a usá-la fora do escopo do bloco "se" subsequente, você está bem. A preocupação de segurança que levantei (em torno de campos que alteram seu valor) é uma preocupação genuína com o código mostrado - sua preocupação de segurança exige que os desenvolvedores sejam negligentes com outro código.
Jon Skeet
11
+1 para apontar que é / cast ou as / cast não é mais lento na realidade, lembre-se. Tendo realizado um teste completo, posso confirmar que não faz diferença, tanto quanto posso ver - e, francamente, você pode executar um número incomparável de elencos em um tempo muito pequeno. Atualizará minha resposta com o código completo.
Jon Skeet
11
De fato, se a ligação não for local, há uma chance de um bug do TOCTTOU (hora do check-to-time-of-use), então é um bom argumento. Quanto ao motivo pelo qual é mais seguro, trabalho com muitos desenvolvedores juniores que gostam de reutilizar os habitantes locais por algum motivo. cast-and-null é, portanto, um risco muito real em minha experiência, e nunca me deparei com uma situação TOCTTOU, pois não desenvolvo meu código dessa maneira. Quanto à velocidade do teste de tempo de execução, é ainda mais rápida que o despacho virtual [1]! Re: código, vou ver se consigo encontrar a fonte para o teste de elenco. [1] upperlogics.blogspot.com/2008/10/…
naasking:
11
@naasking: Eu nunca tive o problema de reutilização local - mas eu diria que é mais fácil identificar na revisão de código do que o bug mais sutil do TOCTTOU. Também vale ressaltar que eu apenas executei novamente meu próprio benchmark verificando interfaces em vez de uma classe selada, e isso sugere o desempenho em favor do cheque como nulo ... mas, como eu disse, o desempenho não é é por isso que eu escolheria uma abordagem particular aqui.
precisa
4

Se a conversão falhar, a palavra-chave 'as' não gera uma exceção; ele define a variável como nula (ou seu valor padrão para tipos de valor).

O Smurf
fonte
3
Nenhum valor padrão para tipos de valor. Como não pode ser usado para converter tipos de valor.
Patrik Hägne 30/01/09
2
A palavra-chave 'as' não funciona realmente com tipos de valor; portanto, sempre define como nulo.
Erik van Brakel
4

Esta não é uma resposta para a pergunta, mas comente o exemplo de código da pergunta:

Normalmente, você não precisa converter um objeto de, por exemplo, IMyInterface para MyClass. O melhor das interfaces é que, se você pegar um objeto como entrada que implementa uma interface, não precisará se importar com o tipo de objeto que está recebendo.

Se você converter IMyInterface para MyClass, já presume que obtém um objeto do tipo MyClass e não faz sentido usar IMyInterface, porque se você alimentar seu código com outras classes que implementam IMyInterface, ele quebrará seu código ...

Agora, meu conselho: se suas interfaces forem bem projetadas, você poderá evitar muitas rotulagens.

f3lix
fonte
3

O asoperador pode ser usado apenas em tipos de referência, não pode ser sobrecarregado e retornará nullse a operação falhar. Isso nunca lançará uma exceção.

A transmissão pode ser usada em qualquer tipo compatível, pode ser sobrecarregada e gerará uma exceção se a operação falhar.

A escolha de qual usar depende das circunstâncias. Principalmente, é uma questão de se você deseja lançar uma exceção em uma conversão com falha.

Jeffrey L Whitledge
fonte
11
'as' também pode ser usado em tipos de valores anuláveis, o que fornece um padrão interessante. Veja minha resposta para o código.
31909 Jon Skeet
1

Minha resposta é apenas sobre velocidade nos casos em que não verificamos o tipo e não verificamos nulos após a transmissão. Adicionei dois testes adicionais ao código de Jon Skeet:

using System;
using System.Diagnostics;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];

        for (int i = 0; i < Size; i++)
        {
            values[i] = "x";
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);

        FindLengthWithCast(values);
        FindLengthWithAs(values);

        Console.ReadLine();
    }

    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);
    }
    static void FindLengthWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = (string)o;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

Resultado:

Is and Cast: 30000000 : 88
Is and As: 30000000 : 93
As and null check: 30000000 : 56
Cast: 30000000 : 66
As: 30000000 : 46

Não tente se concentrar na velocidade (como eu fiz) porque tudo isso é muito, muito rápido.

CoperNick
fonte
Da mesma forma, em meus testes, descobri que a asconversão (sem a verificação de erros) era cerca de 1-3% mais rápida que a conversão (cerca de 540ms versus 550ms em 100 milhões de iterações). Nem fará nem interromperá seu aplicativo.
palswim
1

Além de tudo o que já foi exposto aqui, me deparei com uma diferença prática que acho que vale a pena notar, entre elenco explícito

var x = (T) ...

versus usar o asoperador.

Aqui está o exemplo:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GenericCaster<string>(12345));
        Console.WriteLine(GenericCaster<object>(new { a = 100, b = "string" }) ?? "null");
        Console.WriteLine(GenericCaster<double>(20.4));

        //prints:
        //12345
        //null
        //20.4

        Console.WriteLine(GenericCaster2<string>(12345));
        Console.WriteLine(GenericCaster2<object>(new { a = 100, b = "string" }) ?? "null");

        //will not compile -> 20.4 does not comply due to the type constraint "T : class"
        //Console.WriteLine(GenericCaster2<double>(20.4));
    }

    static T GenericCaster<T>(object value, T defaultValue = default(T))
    {
        T castedValue;
        try
        {
            castedValue = (T) Convert.ChangeType(value, typeof(T));
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }

    static T GenericCaster2<T>(object value, T defaultValue = default(T)) where T : class
    {
        T castedValue;
        try
        {
            castedValue = Convert.ChangeType(value, typeof(T)) as T;
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }
}

Conclusão: GenericCaster2 não funcionará com tipos de estrutura. GenericCaster irá.

Veverke
fonte
1

Se você usar os PIAs do Office visando o .NET Framework 4.X você deve usar o como palavra-chave, caso contrário não irá compilar.

Microsoft.Office.Interop.Outlook.Application o = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.MailItem m = o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Microsoft.Office.Interop.Outlook.MailItem;

A transmissão está correta quando o .NET 2.0 é direcionado:

Microsoft.Office.Interop.Outlook.MailItem m = (Microsoft.Office.Interop.Outlook.MailItem)o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);

Ao direcionar o .NET 4.X, os erros são:

erro CS0656: O compilador ausente exigia o membro 'Microsoft.CSharp.RuntimeBinder.Binder.Convert'

erro CS0656: O compilador ausente exigia o membro 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'

Olivier MATROT
fonte
0

A aspalavra-chave funciona da mesma forma que uma conversão explícita entre tipos de referência compatíveis, com a principal diferença de que não gera uma exceção se a conversão falhar. Em vez disso, gera um valor nulo na variável de destino. Como as exceções são muito caras em termos de desempenho, é considerado um método muito melhor de transmissão.

Cerebrus
fonte
Não é o mesmo, pois um chama CastClass e o outro chama IsInst no código IL.
Jenix
0

O que você escolhe depende muito do que é necessário. Eu prefiro elenco explícito

IMyInterface = (IMyInterface)someobj;

porque se o objeto deve do tipo IMyInterface e não é - é definitivamente um problema. É melhor obter o erro o mais cedo possível, porque o erro exato será corrigido em vez de corrigir o efeito colateral.

Mas se você lida com métodos que aceitam objectcomo parâmetro, precisa verificar seu tipo exato antes de executar qualquer código. Nesse caso, asseria útil para que você possa evitar InvalidCastException.

Oleg
fonte
0

Depende, você deseja verificar a nulidade depois de usar "como" ou prefere que seu aplicativo lance uma exceção?

Minha regra geral é se eu sempre espero que a variável seja do tipo que estou esperando no momento em que quero usar uma conversão. Se for possível que a variável não seja convertida para o que eu quero e estou preparado para lidar com nulos de usar como, usarei como.

Darryl Braaten
fonte
0

O problema do OP é limitado a uma situação de elenco específica. O título cobre muito mais situações.
Aqui está uma visão geral de todas as situações relevantes de elenco que atualmente posso pensar:

private class CBase
{
}

private class CInherited : CBase
{
}

private enum EnumTest
{
  zero,
  one,
  two
}

private static void Main (string[] args)
{
  //########## classes ##########
  // object creation, implicit cast to object
  object oBase = new CBase ();
  object oInherited = new CInherited ();

  CBase oBase2 = null;
  CInherited oInherited2 = null;
  bool bCanCast = false;

  // explicit cast using "()"
  oBase2 = (CBase)oBase;    // works
  oBase2 = (CBase)oInherited;    // works
  //oInherited2 = (CInherited)oBase;   System.InvalidCastException
  oInherited2 = (CInherited)oInherited;    // works

  // explicit cast using "as"
  oBase2 = oBase as CBase;
  oBase2 = oInherited as CBase;
  oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
  oInherited2 = oInherited as CInherited;

  // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
  bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
  bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
  bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
  bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true

  //########## value types ##########
  int iValue = 2;
  double dValue = 1.1;
  EnumTest enValue = EnumTest.two;

  // implicit cast, explicit cast using "()"
  int iValue2 = iValue;   // no cast
  double dValue2 = iValue;  // implicit conversion
  EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')

  iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
  dValue2 = dValue;
  enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"

  iValue2 = (int)enValue;
  dValue2 = (double)enValue;
  enValue2 = enValue;   // no cast

  // explicit cast using "as"
  // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
}
Tobias Knauss
fonte