Casting e conversão de tipos são formas diferentes de mudar uma expressão de um tipo de dado para outro.
A documentação do C# classifica a conversão de tipos como o uso do operador as ou do operador is. Já o casting é definido como o uso do operador de cast.
Em C#, muitas vezes, fora do universo dos valores numéricos e das hierarquias de classes, você vai precisar fazer uma conversão chamando algum membro do tipo de destino, como Int32.Parse(), que converte uma string em um inteiro, ou chamando um membro do tipo de origem, por exemplo object.ToString(). Desenvolvedores de Javascript e de outras linguagens dinâmicas devem ficar atentos.
Repare que existem operadores de cast implícito e explícito (discutidos em operator-overloading) que permitem aplicar casting bastante arbitrário aos seus próprios tipos.
O sistema de tipos do C# é um pouco mais rígido que o de C ou o de Javascript e, como consequência, as operações de casting são mais restritas. O casting implícito acontece entre dois tipos numéricos desde que o tipo de destino consiga preservar a escala e o sinal do valor do tipo de origem. Repare na documentação a exceção para a conversão em números reais, em que a precisão pode ser perdida.
Um cast implícito não é indicado por nenhuma sintaxe especial. Por exemplo:
int myInt = 1729;
long myLong = myInt;
Não existe conversão implícita de uma expressão numérica (ou de string) para bool. A Base Class Library (BCL) fornece Convert.ToBoolean() para isso.
Uma expressão do tipo char pode receber cast implícito para int. O cast na direção oposta precisa ser explícito. Nem todos os valores de int são caracteres utf 16 válidos.
Quando tipos numéricos não podem receber cast implícito, em geral você pode usar o operador de cast explícito.
Quando o valor que está recebendo cast não pode ser representado pelo tipo de destino por não ser largo o suficiente ou por haver conflito de sinal, pode ser lançada uma exceção de overflow no caso dos inteiros, ou a variável do tipo de destino pode assumir o valor Infinity no caso de floats e doubles.
Uma expressão do tipo int pode receber cast explícito para char. Isso pode resultar em um char inválido.
int largeInt = Int32.MaxValue;
int largeNegInt = Int32.MinValue;
ushort shortUnsignedInt = ushort.MaxValue;
float largeFloat = float.MaxValue;
float smallFloat = 17.29f;
// implicit cast
int from_ushort = shortUnsignedInt; // 65535
float from_int = largeInt; // -21474836E+09
int from_char = 'a'; // 96
// explicit cast
uint from_largeInt = (uint)largeInt; // 2147483647
uint from_neg = (uint) largeNegInt; // 2147483648 or OverflowException is thrown (if checked)
int from_smallFloat = (int) smallFloat; // 17
int from_largeFloat = (int) largeFloat; // -2147483648 or OverflowException is thrown (if checked)
char from_intc = (char) 32; // ' '
char from_invalid_int = (char) 0xdcbf; // invalid char - no exception thrown
// no cast available
int fromString = Int32.Parse("42"); // 42
string toString = largeInt.ToString(); // "2147483647"
int fromString_bad = Int32.Parse("forty two"); // FormatException is thrown
Veja este artigo sobre a palavra-chave checked.
Qualquer tipo pode ser convertido implicitamente para sua classe base ou interface.
IList<int> ll = new List<int>();
Um operador de cast pode ser usado para converter uma classe base em uma classe derivada.
IList<int> ll = new List<int>();
List<int> l = (List<int>)ll;
Se o cast falhar porque o tipo da expressão que está recebendo cast não tem relação com o tipo de destino, é lançada uma instância de InvalidCastException.
Uma abordagem mais comum para essas conversões de tipo é usar a palavra-chave is:
interface IFoo { int Bar(); }
class Foo : IFoo { public int Bar() { return 42; }}
Object r = new Random();
IFoo ifoo = new Foo();
Object o = new Foo();
int result = 1729;
if (ifoo is Foo foo)
{
result = foo.Bar();
}
// result == 42
result = 1729;
if (o is Foo foo2)
{
result = foo2.Bar();
}
// result == 42
result = 1729;
if (r is Foo foo3)
{
result = foo3.Bar();
}
// result == 1729
A palavra-chave as cumpre uma função parecida com a de is, por exemplo var foo = ifoo as Foo;. Neste exemplo, foo será null se ifoo não for do tipo Foo; caso contrário, ifoo será atribuído a ele.
Os tipos podem definir seus próprios operadores de cast explícitos e implícitos personalizados.
Exemplos de casts explícitos e implícitos na BCL são as conversões entre a struct BigInteger e outros tipos numéricos.
typeof
Se você precisa detectar o tipo exato de um objeto, is pode ser um pouco permissivo demais, pois converte um objeto para uma classe ou qualquer uma de suas classes base. typeof e Object.GetType() são a solução nesse caso.
object o = new List<int>();
o is ICollection<int> // true
o.GetType() == typeof(ICollection<int>) // false
o is List<int> // true
o.GetType() == typeof(List<int>) // true