web-dev-qa-db-fra.com

Les paramètres peuvent-ils être constants?

Je recherche l'équivalent C # du final de Java. Existe-t-il?

C # a-t-il quelque chose comme ceci:

public Foo(final int bar);

Dans l'exemple ci-dessus, bar est une variable en lecture seule et ne peut pas être modifiée par Foo(). Existe-t-il un moyen de le faire en C #?

Par exemple, j'ai peut-être une longue méthode qui fonctionnera avec les coordonnées x, y et z d'un objet (ints). Je veux être absolument certain que la fonction ne modifie en rien ces valeurs, corrompant ainsi les données. Je voudrais donc les déclarer en lecture seule.

public Foo(int x, int y, int z) {
     // do stuff
     x++; // oops. This corrupts the data. Can this be caught at compile time?
     // do more stuff, assuming x is still the original value.
}
78
Nick Heiner

Malheureusement, vous ne pouvez pas le faire en C #.

Le mot clé const ne peut être utilisé que pour les variables et champs locaux.

Le mot clé readonly ne peut être utilisé que sur les champs.

REMARQUE: le langage Java prend également en charge les paramètres finaux d'une méthode. Cette fonctionnalité est inexistante en C #.

de http://www.25hoursaday.com/CsharpVsJava.html

EDIT (13/08/2019): Je jette cela pour la visibilité car cela est accepté et le plus élevé de la liste. C'est désormais possible avec les paramètres in. Voir la réponse ci-dessous pour plus de détails.

59
Corey Sunwold

Ceci est désormais possible dans C # version 7.2:

Vous pouvez utiliser le mot clé in dans la signature de la méthode. documentation MSDN .

Le mot clé in doit être ajouté avant de spécifier l'argument d'une méthode.

Exemple, une méthode valide en C # 7.2:

public long Add(in long x, in long y)
{
    return x + y;
}

Bien que ce qui suit ne soit pas autorisé:

public long Add(in long x, in long y)
{
    x = 10; // It is not allowed to modify an in-argument.
    return x + y;
}

L'erreur suivante sera affichée lors de la tentative de modification de x ou y car elles sont marquées avec in:

Impossible d'affecter à la variable 'in long' car il s'agit d'une variable en lecture seule

Marquer un argument avec in signifie:

Cette méthode ne modifie pas la valeur de l'argument utilisé comme paramètre.

22
Max

Je vais commencer par la partie int. int est un type de valeur, et dans .Net cela signifie que vous avez vraiment affaire à une copie. C'est une contrainte de conception vraiment bizarre de dire à une méthode "Vous pouvez avoir une copie de cette valeur. C'est votre copie, pas la mienne; je ne la reverrai plus jamais. Mais vous ne pouvez pas changer la copie." Il est implicite dans l'appel de méthode que la copie de cette valeur est correcte, sinon nous n'aurions pas pu appeler la méthode en toute sécurité. Si la méthode a besoin de l'original, laissez à l'implémenteur le soin de faire une copie pour l'enregistrer. Donnez la valeur à la méthode ou ne donnez pas de valeur à la méthode. Ne faites pas tous les vœux entre les deux.

Passons aux types de référence. Maintenant, cela devient un peu déroutant. Voulez-vous dire une référence constante, où la référence elle-même ne peut pas être modifiée, ou un objet complètement verrouillé et immuable? Dans le premier cas, les références par défaut dans .Net sont transmises par valeur. Autrement dit, vous obtenez une copie de la référence. Nous avons donc essentiellement la même situation que pour les types de valeur. Si le réalisateur a besoin de la référence d'origine, il peut le conserver lui-même.

Cela nous laisse juste avec un objet constant (verrouillé/immuable). Cela peut sembler correct du point de vue de l'exécution, mais comment le compilateur peut-il l'appliquer? Étant donné que les propriétés et les méthodes peuvent toutes avoir des effets secondaires, vous seriez essentiellement limité à l'accès aux champs en lecture seule. Un tel objet n'est pas susceptible d'être très intéressant.

8
Joel Coehoorn

La réponse: C # n'a pas la fonctionnalité const comme C++.

Je suis d'accord avec Bennett Dill.

Le mot clé const est très utile. Dans l'exemple, vous avez utilisé un int et les gens n'obtiennent pas votre point. Mais, pourquoi si votre paramètre est un objet utilisateur énorme et complexe qui ne peut pas être modifié à l'intérieur de cette fonction? C'est l'utilisation du mot clé const: le paramètre ne peut pas changer à l'intérieur de cette méthode car [quelle qu'en soit la raison ici] cela n'a pas d'importance pour cette méthode. Le mot clé const est très puissant et il me manque vraiment en C #.

8
Michel Vaz Ramos

Voici une réponse courte et douce qui obtiendra probablement beaucoup de votes négatifs. Je n'ai pas lu tous les messages et commentaires, alors pardonnez-moi si cela a déjà été suggéré.

Pourquoi ne pas prendre vos paramètres et les passer dans un objet qui les expose comme immuables et ensuite utiliser cet objet dans votre méthode?

Je me rends compte que c'est probablement un travail très évident qui a déjà été envisagé et le PO essaie d'éviter de le faire en posant cette question, mais je pensais que cela devrait être ici néanmoins ...

Bonne chance :-)

7
Bennett Dill

Créez une interface pour votre classe qui n'a que des accesseurs de propriété en lecture seule. Demandez ensuite à votre paramètre d'être de cette interface plutôt que de la classe elle-même. Exemple:

public interface IExample
{
    int ReadonlyValue { get; }
}

public class Example : IExample
{
    public int Value { get; set; }
    public int ReadonlyValue { get { return this.Value; } }
}


public void Foo(IExample example)
{
    // Now only has access to the get accessors for the properties
}

Pour les structures, créez un wrapper const générique.

public struct Const<T>
{
    public T Value { get; private set; }

    public Const(T value)
    {
        this.Value = value;
    }
}

public Foo(Const<float> X, Const<float> Y, Const<float> Z)
{
// Can only read these values
}

Il vaut la peine de noter cependant, qu'il est étrange que vous vouliez faire ce que vous demandez de faire concernant les structures, en tant qu'écrivain de la méthode, vous devez vous attendre à savoir ce qui se passe dans cette méthode. Cela n'affectera pas les valeurs transmises pour les modifier dans la méthode, donc votre seule préoccupation est de vous assurer que vous vous comportez bien dans la méthode que vous écrivez. Il arrive un moment où la vigilance et le code propre sont la clé, au-dessus de l'application de const et d'autres règles de ce type.

5
Steve Lillis

Si vous rencontrez souvent des problèmes comme celui-ci, vous devriez envisager des "applications hongroises". Le bon type, par opposition au mauvais type . Bien que cela n'essaie pas normalement d'exprimer la constance d'un paramètre de méthode (c'est tout simplement trop inhabituel), rien ne vous empêche certainement de clouer un "c" supplémentaire avant le nom de l'identifiant.

À tous ceux qui ont envie de claquer le bouton downvote maintenant, veuillez lire les avis de ces luminaires sur le sujet:

3
Hans Passant

Je sais que cela pourrait être un peu tard. Mais pour les personnes qui recherchent toujours d'autres moyens pour cela, il pourrait y avoir un autre moyen de contourner cette limitation de la norme C #. Nous pourrions écrire la classe wrapper ReadOnly <T> où T: struct. Avec conversion implicite en type de base T. Mais uniquement conversion explicite en classe wrapper <T>. Ce qui appliquera les erreurs du compilateur si le développeur essaie de définir implicitement la valeur du type ReadOnly <T>. Comme je vais en démontrer deux utilisations possibles ci-dessous.

L'UTILISATION 1 a nécessité la définition de l'appelant pour changer. Cette utilisation ne sera utilisée que pour tester l'exactitude de votre code de fonctions "TestCalled". Au niveau de la version/builds, vous ne devriez pas l'utiliser. Étant donné qu'à grande échelle, les opérations mathématiques peuvent surpasser les conversions et ralentir votre code. Je ne l'utiliserais pas, mais à des fins de démonstration seulement, je l'ai posté.

L'UTILISATION 2 que je suggérerais, a démontré l'utilisation de Debug vs Release dans la fonction TestCalled2. De plus, il n'y aurait pas de conversion dans la fonction TestCaller lors de l'utilisation de cette approche, mais cela nécessite un peu plus de codage des définitions TestCaller2 à l'aide du conditionnement du compilateur. Vous pouvez remarquer des erreurs de compilation dans la configuration de débogage, tandis que lors de la configuration de la version, tout le code de la fonction TestCalled2 se compilera correctement.

using System;
using System.Collections.Generic;

public class ReadOnly<VT>
  where VT : struct
{
  private VT value;
  public ReadOnly(VT value)
  {
    this.value = value;
  }
  public static implicit operator VT(ReadOnly<VT> rvalue)
  {
    return rvalue.value;
  }
  public static explicit operator ReadOnly<VT>(VT rvalue)
  {
    return new ReadOnly<VT>(rvalue);
  }
}

public static class TestFunctionArguments
{
  static void TestCall()
  {
    long a = 0;

    // CALL USAGE 1.
    // explicite cast must exist in call to this function
    // and clearly states it will be readonly inside TestCalled function.
    TestCalled(a);                  // invalid call, we must explicit cast to ReadOnly<T>
    TestCalled((ReadOnly<long>)a);  // explicit cast to ReadOnly<T>

    // CALL USAGE 2.
    // Debug vs Release call has no difference - no compiler errors
    TestCalled2(a);

  }

  // ARG USAGE 1.
  static void TestCalled(ReadOnly<long> a)
  {
    // invalid operations, compiler errors
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  }


  // ARG USAGE 2.
#if DEBUG
  static void TestCalled2(long a2_writable)
  {
    ReadOnly<long> a = new ReadOnly<long>(a2_writable);
#else
  static void TestCalled2(long a)
  {
#endif
    // invalid operations
    // compiler will have errors in debug configuration
    // compiler will compile in release
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    // compiler will compile in both, debug and release configurations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  }

}
2
SoLaR

Si struct est passé dans une méthode, à moins qu'il ne soit passé par ref, il ne sera pas modifié par la méthode dans laquelle il est passé. Donc, dans ce sens, oui.

Pouvez-vous créer un paramètre dont la valeur ne peut pas être affectée dans la méthode ou dont les propriétés ne peuvent pas être définies dans la méthode? Non. Vous ne pouvez pas empêcher l'affectation de la valeur dans la méthode, mais vous pouvez empêcher ses propriétés d'être définies en créant un type immuable.

La question n'est pas de savoir si le paramètre ou ses propriétés peuvent être attribués dans la méthode. La question est de savoir ce que ce sera à la fin de la méthode.

La seule fois où des données externes vont être modifiées, c'est si vous passez une classe et modifiez l'une de ses propriétés, ou si vous transmettez une valeur à l'aide du mot clé ref. La situation que vous avez décrite ne fait ni l'un ni l'autre.

0
David Morton