Apenas por uma questão de integridade, aqui está um despejo cerebral de informações relacionadas ...
Como outros observaram, stringé um apelido para System.String. Eles são compilados com o mesmo código, portanto, no tempo de execução, não há diferença alguma. Este é apenas um dos aliases em C #. A lista completa é:
object: System.Object
string: System.String
bool: System.Boolean
byte: System.Byte
sbyte: System.SByte
short: System.Int16
ushort: System.UInt16
int: System.Int32
uint: System.UInt32
long: System.Int64
ulong: System.UInt64
float: System.Single
double: System.Double
decimal: System.Decimal
char: System.Char
Além de stringe object, os alias são todos para tipos de valor. decimalé um tipo de valor, mas não um tipo primitivo no CLR. O único tipo primitivo que não possui um alias é System.IntPtr.
Na especificação, os aliases de tipo de valor são conhecidos como "tipos simples". Os literais podem ser usados para valores constantes de todos os tipos simples; nenhum outro tipo de valor tem formas literais disponíveis. (Compare isso com o VB, que permite DateTimeliterais e também possui um alias.)
Há uma circunstância em que você precisa usar os aliases: ao especificar explicitamente o tipo subjacente de uma enumeração. Por exemplo:
public enum Foo : UInt32 {} // Invalid
public enum Bar : uint {} // Valid
Isso é apenas uma questão de como a especificação define enum declarações - a parte após os dois pontos tem que ser o tipo integrante de produção, que é um símbolo de sbyte, byte, short, ushort, int, uint, long, ulong, char... em oposição a um tipo de produção como usado por declarações variáveis, por exemplo. Não indica nenhuma outra diferença.
Finalmente, quando se trata de usar: pessoalmente, uso os aliases em todos os lugares para a implementação, mas o tipo CLR para qualquer API. Realmente não importa muito o que você usa em termos de implementação - a consistência entre sua equipe é boa, mas ninguém mais vai se importar. Por outro lado, é realmente importante que, se você se referir a um tipo em uma API, faça isso de maneira neutra em termos de idioma. Um método chamado ReadInt32é inequívoco, enquanto um método chamado ReadIntrequer interpretação. O chamador pode estar usando um idioma que define um intalias para Int16, por exemplo. Os designers .NET framework seguiram esse padrão, bons exemplos sendo nos BitConverter, BinaryReadere Convertclasses.
stringé uma construção lexical da gramática C #, enquantoSystem.Stringé apenas um tipo. Independentemente de qualquer diferença explícita mencionada em qualquer especificação, ainda existe essa diferença implícita que pode ser acomodada com alguma ambiguidade. A própria linguagem deve suportarstringde uma maneira que a implementação não seja (completamente) obrigada a considerar para uma classe específica na BCL.