Por que o C # não suporta o retorno de referências?


141

Eu li que o .NET suporta retorno de referências, mas o C # não. Existe um motivo especial? Por que não posso fazer algo como:

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

8
um ... citação por favor?
RPM1984

5
C # tem tipos de valor e referência e ambos podem ser retornados, o que você quer dizer.
executar novamente

Estou falando de algo comoreturn ref x
Tom Sarduy

2
Após 4 anos, você pode fazer exatamente isso com C# 7:)
Arghya C

Respostas:


188

Esta questão foi o assunto do meu blog em 23 de junho de 2011 . Obrigado pela ótima pergunta!

A equipe do C # está considerando isso para o C # 7. Consulte https://github.com/dotnet/roslyn/issues/5233 para obter detalhes.

UPDATE: O recurso chegou ao C # 7!


Você está certo; O .NET oferece suporte a métodos que retornam referências gerenciadas para variáveis. O .NET também suporta variáveis ​​locais que contêm referências gerenciadas para outras variáveis. (Observe, no entanto, que o .NET não oferece suporte a campos ou matrizes que contêm referências gerenciadas para outras variáveis, pois isso complica demais a história da coleta de lixo. Além disso, os tipos de "referência gerenciada à variável" não são conversíveis em objeto e, portanto, não podem ser usados ​​como argumentos de tipo para tipos ou métodos genéricos.)

O comentarista "RPM1984", por algum motivo, pediu uma citação para esse fato. RPM1984 Convido você a ler a Partição I da Seção 8.2.1.1 da especificação CLI, "Ponteiros gerenciados e tipos relacionados" para obter informações sobre esse recurso do .NET.

É totalmente possível criar uma versão do C # que ofereça suporte a esses dois recursos. Você poderia então fazer coisas como

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

e depois chame-o com

int a = 123;
int b = 456; 
ref int c = ref Max(ref a, ref b); 
c += 100;
Console.WriteLine(b); // 556!

Eu sei empiricamente que é possível criar uma versão do C # que ofereça suporte a esses recursos, porque eu o fiz . Programadores avançados, particularmente pessoas portadoras de código C ++ não gerenciado, geralmente nos pedem mais capacidade semelhante a C ++ para fazer coisas com referências sem precisar sair do grande martelo de usar ponteiros e fixar memória em todo o lugar. Ao usar referências gerenciadas, você obtém esses benefícios sem pagar o custo de estragar o desempenho da coleta de lixo.

Consideramos esse recurso e, na verdade, implementamos o suficiente para mostrar a outras equipes internas para obter feedback. No entanto, no momento, com base em nossa pesquisa , acreditamos que o recurso não possui apelo suficientemente amplo ou casos de uso convincentes para transformá-lo em um recurso real de idioma suportado . Temos outras prioridades mais altas e uma quantidade limitada de tempo e esforço disponível; portanto, não faremos esse recurso tão cedo.

Além disso, fazê-lo corretamente exigiria algumas alterações no CLR. No momento, o CLR trata os métodos de retorno de retorno como legais, mas não verificáveis, porque não temos um detector que detecte essa situação:

ref int M1(ref int x)
{
    return ref x;
}

ref int M2()
{
    int y = 123;
    return ref M1(ref y); // Trouble!
}

int M3()
{
    ref int z = ref M2();
    return z;
}

M3 retorna o conteúdo da variável local do M2, mas o tempo de vida dessa variável terminou! É possível escrever um detector que determine o uso de retornos ref que claramente não violam a segurança da pilha. O que faríamos é escrever um detector desse tipo e, se o detector não pudesse provar a segurança da pilha, não permitiríamos o uso de retornos de ref nessa parte do programa. Não é uma quantidade enorme de trabalho para desenvolvedores fazer isso, mas é muito oneroso para as equipes de teste garantir que realmente tenhamos todos os casos. É apenas outra coisa que aumenta o custo do recurso a um ponto em que, no momento, os benefícios não superam os custos.

Se você pode descrever para mim por que você deseja esse recurso, eu realmente aprecio isso . Quanto mais informações tivermos de clientes reais sobre o motivo pelo qual eles desejam, maior a probabilidade de ele entrar no produto algum dia. É um recurso bonitinho e eu gostaria de poder levá-lo aos clientes de alguma forma, se houver interesse suficiente.

(Consulte também perguntas relacionadas É possível retornar uma referência a uma variável em C #? E posso usar uma referência dentro de uma função C # como C ++? )


4
@ EricLippert: Eu não tenho exemplo convincente é apenas algo que eu queria saber. Excelente e convincente resposta
Tom Sarduy

1
@ Eric: No seu exemplo, não seria mais adequado manter-se y vivo depois de voltar M2? Eu esperaria que esse recurso funcionasse como lambdas capturando locais. Ou é o comportamento que você propôs porque é como o CLR lida com esse cenário?
Fede

3
@ Eric: IMHO a capacidade de ter propriedades retornando referências a tipos de valor é uma grande omissão nos idiomas .net. Se Arr é uma matriz de um tipo de valor (por exemplo, ponto), pode-se dizer, por exemplo, Arr (3) .X = 9 e saber que não foi alterado o valor de Arr (9) .X ou mesmo SomeOtherArray (2). X; se Arr fosse uma matriz de algum tipo de referência, essas garantias não existiriam. O fato de o operador de indexação de uma matriz retornar uma referência é extremamente útil; Considero altamente lamentável que nenhum outro tipo de coleção possa fornecer essa funcionalidade.
precisa

4
Eric, qual é a melhor maneira de fornecer feedback sobre os cenários (como você sugere no último parágrafo) para maximizar as chances de ser visto? MS Connect? UserVoice (com seu limite arbitrário de 10 publicações / votos)? Algo mais?
Roman Starkov

1
@ThunderGr: Essa é a filosofia do C # para "não seguro" - se você escrever um código possivelmente inseguro para a memória, o C # insistirá em marcá-lo como "não seguro", para que você assuma a responsabilidade pela segurança da memória. O C # já possui a versão não segura do recurso, desde que a variável em questão seja do tipo não gerenciado. A questão é se a equipe de C # deve criar uma versão não segura que lide com tipos gerenciados. Se isso facilitar para o desenvolvedor escrever bugs terríveis, isso não acontecerá. C # não é C ++, uma linguagem que facilita escrever bugs terríveis. C # é seguro por design.
precisa

21

Você está falando de métodos que retornam uma referência a um tipo de valor. O único exemplo interno em C # que eu conheço é o acessador de matriz de um tipo de valor:

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

e agora crie uma matriz dessa estrutura:

var points = new Point[10];
points[0].X = 1;
points[0].Y = 2;

Nesse caso points[0], o indexador de matriz está retornando uma referência para struct. É impossível escrever seu próprio indexador (por exemplo, para uma coleção personalizada), que tenha o mesmo comportamento "retornar uma referência".

Eu não projetei a linguagem C #, então não conheço todo o raciocínio por trás de não suportá-la, mas acho que a resposta curta pode ser: podemos nos dar bem sem ela.


8
Tom está perguntando sobre métodos que retornam uma referência a uma variável . A variável não precisa ser do tipo valor, embora é claro que geralmente é o que as pessoas desejam quando desejam métodos de retorno de retorno. Caso contrário, ótima análise; você está certo de que o único lugar na linguagem C # em que uma expressão complexa produz uma ref para uma variável que o usuário pode manipular é o indexador de matriz. (E, claro, o operador de acesso de membros entre um receptor e um campo, mas que é bastante obviamente um acesso a uma variável "".)
Eric Lippert

1

Você sempre pode fazer algo como:

public delegate void MyByRefConsumer<T>(ref T val);

public void DoSomethingWithValueType(MyByRefConsumer<int> c)
{
        int x = 2;
        c(ref x);
        //Handle potentially changed x...
}

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.