O casting e a conversão de tipos são formas diferentes de alterar uma expressão de um tipo de dados 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 domínio dos valores numéricos e das hierarquias de classes, tens de fazer uma conversão chamando algum membro do tipo de destino, como Int32.Parse(), que converte uma string num inteiro, ou chamando um membro do tipo de origem, por exemplo object.ToString(). Os programadores de Javascript e de outras linguagens dinâmicas devem estar atentos a isto.
Repara que estão disponíveis operadores de cast implícitos e explícitos (abordados em operator-overloading), que te permitem aplicar casting bastante arbitrário aos teus próprios tipos.
O sistema de tipos do C# é um pouco mais restrito do que o de C ou o de Javascript e, por consequência, as operações de casting são mais limitadas. O casting implícito ocorre entre dois tipos numéricos desde que o tipo de destino consiga preservar a escala e o sinal do valor do tipo de origem. Repara, na documentação, na exceção para a conversão em números reais, onde a precisão pode perder-se.
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 string) para bool. A Base Class Library (BCL) fornece Convert.ToBoolean() para esse efeito.
Uma expressão do tipo char pode ser convertida implicitamente em int. O cast na direção oposta tem de ser explícito. Nem todos os valores de int são carateres utf 16 válidos.
Quando os tipos numéricos não podem ser convertidos implicitamente, podes geralmente usar o operador de cast explícito.
Quando o valor que está a ser convertido não pode ser representado pelo tipo de destino, por não ser suficientemente largo ou por haver um 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 dos números de vírgula flutuante.
Uma expressão do tipo int pode ser convertida explicitamente em char. Isto pode resultar num 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
Vê este artigo para saberes mais sobre a palavra-chave checked.
Qualquer tipo pode ser convertido implicitamente na sua classe base ou interface.
IList<int> ll = new List<int>();
Podes usar um operador de cast para converter de uma classe base numa 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á a ser convertida não está relacionado com o tipo para o qual está a ser convertida, é lançada uma instância de InvalidCastException.
Uma abordagem mais habitual a estas conversões de tipos é 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 desempenha uma função semelhante a 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-lhe-á atribuído.
Os tipos podem definir os seus próprios operadores de cast explícitos e implícitos.
Exemplos de casts explícitos e implícitos na BCL são as conversões da struct BigInteger de e para outros tipos numéricos
typeof
Se precisares de detetar o tipo exato de um objeto, o is pode ser um pouco permissivo demais, pois converte um objeto numa classe ou em qualquer uma das suas classes base. Neste caso, a solução é typeof e Object.GetType().
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