Pourquoi C # ne prend-il pas en charge le retour de références?

141

J'ai lu que .NET prend en charge le retour de références, mais pas C #. Y a-t-il une raison particulière? Pourquoi je ne peux pas faire quelque chose comme:

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 
Tom Sarduy
la source
8
euh ... citation s'il vous plaît?
RPM1984 le
5
C # a des types valeur et référence et les deux peuvent être retournés que voulez-vous dire.
rediffusé le
Je parle de quelque chose commereturn ref x
Tom Sarduy
2
Après 4 ans, vous pouvez faire exactement cela avec C# 7:)
Arghya C

Réponses:

188

Cette question a fait l'objet de mon blog le 23 juin 2011 . Merci pour la bonne question!

L'équipe C # envisage cela pour C # 7. Voir https://github.com/dotnet/roslyn/issues/5233 pour plus de détails.

MISE À JOUR: La fonctionnalité est arrivée en C # 7!


Vous avez raison; .NET prend en charge les méthodes qui renvoient des références managées à des variables. .NET prend également en charge les variables locales qui contiennent des références managées à d'autres variables. (Notez cependant que .NET ne prend pas en charge les champs ou les tableaux qui contiennent des références gérées à d'autres variables car cela complique excessivement l'histoire du garbage collection. De plus, les types «référence gérée à une variable» ne sont pas convertibles en objet et ne peuvent donc pas être utilisés comme les arguments de type aux types ou méthodes génériques.)

Le commentateur "RPM1984" a pour une raison quelconque demandé une citation pour ce fait. RPM1984 Je vous encourage à lire la spécification CLI Partition I Section 8.2.1.1, «Pointeurs gérés et types associés» pour plus d'informations sur cette fonctionnalité de .NET.

Il est tout à fait possible de créer une version de C # qui prend en charge ces deux fonctionnalités. Vous pourriez alors faire des choses comme

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

puis appelez-le avec

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

Je sais empiriquement qu'il est possible de créer une version de C # qui prend en charge ces fonctionnalités parce que je l'ai fait . Les programmeurs avancés, en particulier les personnes qui portent du code C ++ non géré, nous demandent souvent plus de capacité à faire des choses avec des références sans avoir à se débarrasser du gros marteau de l'utilisation de pointeurs et d'épinglage de mémoire partout. En utilisant des références gérées, vous bénéficiez de ces avantages sans payer le coût de la dégradation de vos performances de récupération de place.

Nous avons pris en compte cette fonctionnalité et en avons mis en œuvre suffisamment pour que d'autres équipes internes puissent en bénéficier. Cependant, à l'heure actuelle, sur la base de nos recherches, nous pensons que la fonctionnalité n'a pas un attrait suffisamment large ou des cas d'utilisation convaincants pour en faire une véritable fonctionnalité de langage prise en charge . Nous avons d'autres priorités plus élevées et une quantité limitée de temps et d'efforts disponibles, nous n'allons donc pas faire cette fonctionnalité de si tôt.

En outre, le faire correctement nécessiterait des modifications du CLR. À l'heure actuelle, le CLR traite les méthodes de retour de référence comme légales mais invérifiables car nous n'avons pas de détecteur qui détecte cette situation:

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 renvoie le contenu de la variable locale de M2, mais la durée de vie de cette variable est terminée! Il est possible d'écrire un détecteur qui détermine les utilisations des retours de référence qui ne violent clairement pas la sécurité de la pile. Ce que nous ferions serait d'écrire un tel détecteur, et si le détecteur ne pouvait pas prouver la sécurité de la pile, nous n'autoriserions pas l'utilisation de retours de référence dans cette partie du programme. Ce n'est pas un travail de développement énorme pour le faire, mais c'est beaucoup de travail pour les équipes de test de s'assurer que nous avons vraiment tous les cas. C'est juste une autre chose qui augmente le coût de la fonctionnalité au point où, pour le moment, les avantages ne l'emportent pas sur les coûts.

Si vous pouvez me décrire pourquoi vous voulez cette fonctionnalité, je l'apprécierais vraiment . Plus nous avons d'informations de la part de vrais clients sur les raisons pour lesquelles ils le souhaitent, plus il est probable que cela deviendra le produit un jour. C'est une jolie petite fonctionnalité et j'aimerais pouvoir la proposer aux clients d'une manière ou d'une autre s'il y a suffisamment d'intérêt.

(Voir aussi les questions connexes Est-il possible de renvoyer une référence à une variable en C #? Et Puis-je utiliser une référence dans une fonction C # comme C ++? )

Eric Lippert
la source
4
@EricLippert: Je n'ai pas d'exemple convaincant, c'est juste quelque chose que je me demandais. Réponse excellente et convaincante
Tom Sarduy
1
@Eric: Dans votre exemple, ne serait-il pas plus approprié de rester en y vie après son retour M2? Je m'attendrais à ce que cette fonctionnalité fonctionne comme des lambdas capturant les habitants. Ou est le comportement que vous avez proposé parce que c'est ainsi que CLR gère ce scénario?
Fede
3
@Eric: à mon humble avis, la possibilité d'avoir des propriétés renvoyant des références à des types de valeur est une omission majeure dans les langages .net. Si Arr est un Array de type valeur (par exemple Point), on peut dire par exemple Arr (3) .X = 9 et savoir qu'on n'a pas changé la valeur de Arr (9) .X ou même de SomeOtherArray (2). X; si Arr était plutôt un tableau d'un type de référence, aucune garantie de ce type n'existerait. Le fait que l'opérateur d'indexation d'un tableau renvoie une référence est extrêmement utile; Je considère qu'il est extrêmement regrettable qu'aucun autre type de collection ne puisse offrir une telle fonctionnalité.
supercat
4
Eric, quelle est la meilleure façon de fournir le feedback sur les scénarios (comme vous le suggérez dans le dernier paragraphe) pour maximiser les chances que cela soit vu? MS Connect? UserVoice (avec sa limite arbitraire de 10 postes / votes)? Autre chose?
Roman Starkov
1
@ThunderGr: Telle est la philosophie C # pour "unsafe" - si vous écrivez du code qui est peut-être dangereux pour la mémoire, C # insiste pour que vous le marquiez comme "unsafe", de sorte que vous assumiez la responsabilité de la sécurité de la mémoire. C # a déjà la version non sécurisée de la fonctionnalité, à condition que la variable en question soit de type non managé. La question est de savoir si l'équipe C # doit créer une version non sécurisée qui gère les types gérés. Si cela permet au développeur d'écrire facilement de terribles bogues, cela ne se produira pas. C # n'est pas C ++, un langage qui permet d'écrire facilement de terribles bogues. C # est sûr par conception.
Eric Lippert
21

Vous parlez de méthodes qui renvoient une référence à un type valeur. Le seul exemple intégré en C # que je connaisse est l'accesseur de tableau d'un type valeur:

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

et maintenant créez un tableau de cette structure:

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

Dans ce cas points[0], l' indexeur de tableau renvoie une référence à struct. Il est impossible d'écrire votre propre indexeur (par exemple pour une collection personnalisée), qui a le même comportement «renvoyer une référence».

Je n'ai pas conçu le langage C #, donc je ne connais pas tout le raisonnement derrière le fait de ne pas le soutenir, mais je pense que la réponse courte pourrait être: nous pouvons très bien nous en passer.

Rick Sladkey
la source
8
Tom pose des questions sur les méthodes qui renvoient une référence à une variable . La variable n'a pas besoin d'être de type valeur, bien que bien sûr, c'est généralement ce que les gens veulent quand ils veulent des méthodes de retour de référence. Sinon, excellente analyse; vous avez raison de dire que le seul endroit dans le langage C # où une expression complexe produit une référence à une variable que l'utilisateur peut ensuite manipuler est l'indexeur de tableau. (Et bien sûr l'opérateur d'accès membre "." Entre un récepteur et un champ, mais c'est assez évidemment un accès à une variable.)
Eric Lippert
1

Vous pouvez toujours faire quelque chose comme:

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

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

C # 7.0 prend en charge le renvoi de références. Voir ma réponse ici .

CodageYoshi
la source