web-dev-qa-db-fra.com

La déclaration VB.NET 'With' - embrasser ou éviter?

Au travail, je travaille fréquemment sur des projets où de nombreuses propriétés de certains objets doivent être définies lors de leur construction ou au début de leur vie. Pour des raisons de commodité et de lisibilité, j'utilise souvent l'instruction With pour définir ces propriétés. Je trouve que

With Me.Elements
    .PropertyA = True
    .PropertyB = "Inactive"
    ' And so on for several more lines
End With

Semble beaucoup mieux que

Me.Elements.PropertyA = True
Me.Elements.PropertyB = "Inactive"
' And so on for several more lines

pour les instructions très longues qui définissent simplement les propriétés.

J'ai remarqué qu'il y a des problèmes avec l'utilisation de With lors du débogage; cependant, je me demandais s'il y avait des raisons impérieuses d'éviter d'utiliser With dans la pratique? J'ai toujours supposé que le code généré via le compilateur pour les deux cas ci-dessus était fondamentalement le même, c'est pourquoi j'ai toujours choisi d'écrire ce que je ressens comme étant plus lisible.

67
Tom

Si vous avez des noms de variables longs et que vous vous retrouvez avec:

UserHandler.GetUser.First.User.FirstName="Stefan"
UserHandler.GetUser.First.User.LastName="Karlsson"
UserHandler.GetUser.First.User.Age="39"
UserHandler.GetUser.First.User.Sex="Male"
UserHandler.GetUser.First.User.Occupation="Programmer"
UserHandler.GetUser.First.User.UserID="0"
....and so on

alors j'utiliserais WITH pour le rendre plus lisible:

With UserHandler.GetUser.First.User
    .FirstName="Stefan"
    .LastName="Karlsson"
    .Age="39"
    .Sex="Male"
    .Occupation="Programmer"
    .UserID="0"
end with

Dans le dernier exemple, il y a même des avantages en termes de performances par rapport au premier exemple parce que dans le premier exemple, je récupère l'utilisateur chaque fois que j'accède à une propriété utilisateur et dans le cas AVEC, je ne récupère l'utilisateur qu'une seule fois.

Je peux obtenir le gain de performances sans utiliser avec, comme ceci:

dim myuser as user =UserHandler.GetUser.First.User
myuser.FirstName="Stefan"
myuser.LastName="Karlsson"
myuser.Age="39"
myuser.Sex="Male"
myuser.Occupation="Programmer"
myuser.UserID="0"

Mais j'opterais plutôt pour l'instruction WITH, elle a l'air plus propre.

Et je viens de prendre cela comme exemple, alors ne vous plaignez pas d'une classe avec de nombreux mots clés, un autre exemple pourrait être comme: WITH RefundDialog.RefundDatagridView.SelectedRows (0)

64
Stefan

Dans la pratique, il n'y a pas vraiment d'arguments contraignants. Je ne suis pas un fan, mais c'est une préférence personnelle, il n'y a pas de données empiriques pour suggérer que la construction With est mauvaise.

Dans .NET, il compile exactement le même code que la qualification complète du nom d'objet, il n'y a donc pas de pénalité de performance pour ce sucre. J'ai vérifié cela en compilant, puis en désassemblant, la classe VB .NET 2.0 suivante:

Imports System.Text

Public Class Class1
    Public Sub Foo()
        Dim sb As New StringBuilder
        With sb
            .Append("foo")
            .Append("bar")
            .Append("zap")
        End With

        Dim sb2 As New StringBuilder
        sb2.Append("foo")
        sb2.Append("bar")
        sb2.Append("zap")
    End Sub
End Class

Le démontage est le suivant - notez que les appels à la méthode Append de sb2 Sont identiques aux appels de l'instruction With pour sb:

.method public instance void  Foo() cil managed
{
  // Code size       91 (0x5b)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Text.StringBuilder sb,
           [1] class [mscorlib]System.Text.StringBuilder sb2,
           [2] class [mscorlib]System.Text.StringBuilder VB$t_ref$L0)
  IL_0000:  nop
  IL_0001:  newobj     instance void [mscorlib]System.Text.StringBuilder::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  stloc.2
  IL_0009:  ldloc.2
  IL_000a:  ldstr      "foo"
  IL_000f:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
  IL_0014:  pop
  IL_0015:  ldloc.2
  IL_0016:  ldstr      "bar"
  IL_001b:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
  IL_0020:  pop
  IL_0021:  ldloc.2
  IL_0022:  ldstr      "zap"
  IL_0027:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
  IL_002c:  pop
  IL_002d:  ldnull
  IL_002e:  stloc.2
  IL_002f:  newobj     instance void [mscorlib]System.Text.StringBuilder::.ctor()
  IL_0034:  stloc.1
  IL_0035:  ldloc.1
  IL_0036:  ldstr      "foo"
  IL_003b:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
  IL_0040:  pop
  IL_0041:  ldloc.1
  IL_0042:  ldstr      "bar"
  IL_0047:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
  IL_004c:  pop
  IL_004d:  ldloc.1
  IL_004e:  ldstr      "zap"
  IL_0053:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
  IL_0058:  pop
  IL_0059:  nop
  IL_005a:  ret
} // end of method Class1::Foo

Donc, si vous l'aimez et le trouvez plus lisible, allez-y; il n'y a aucune raison impérieuse de ne pas le faire.

(Au fait, Tom , je voudrais savoir ce qui s'est passé avec le débogueur - je ne me souviens pas avoir vu de comportement inhabituel dans le débogueur basé sur une instruction With , donc je suis curieux de savoir quel comportement vous avez vu.)

24
John Rudy

Il y a une différence entre utiliser With et faire des références répétitives à un objet, ce qui est subtil mais devrait être gardé à l'esprit, je pense.

Lorsqu'une instruction WITH est utilisée, elle crée une nouvelle variable locale référençant l'objet. Les références suivantes utilisant .xx sont des références aux propriétés de cette référence locale. Si lors de l'exécution de l'instruction WITH, la référence de variable d'origine est modifiée, l'objet référencé par WITH ne change pas. Considérer:

Dim AA As AAClass = GetNextAAObject()
With AA
    AA = GetNextAAObject()

    '// Setting property of original AA instance, not later instance
    .SomeProperty = SomeValue
End With

Ainsi, l'instruction WITH n'est pas simplement du sucre syntaxique, c'est vraiment une construction différente. Bien qu'il soit peu probable que vous codiez quelque chose d'explicite comme ci-dessus, dans certaines situations, cela peut se produire par inadvertance, vous devez donc être conscient du problème. La situation la plus probable est celle où vous pouvez traverser une structure telle qu'un réseau d'objets dont les interconnexions peuvent être implicitement modifiées en définissant des propriétés.

15
JohnRC

Tout est question de lisibilité. Comme tout sucre syntaxique, il peut être surutilisé.

Embrace it IF vous définissez plusieurs membres d'un objet sur quelques lignes

With myObject
  .Property1 = arg1
  .Property2 = arg2
...

éviter faire autre chose avec "avec"

Si vous écrivez un bloc With qui s'étend sur 50-100 lignes et implique de nombreuses autres variables, il peut être très difficile de se souvenir de ce qui a été déclaré en haut du bloc. Pour des raisons évidentes, je ne fournirai pas d'exemple de code aussi salissant

11
phillihp

Là où il rend le code véritablement plus lisible, allez-y. Où cela le rend moins lisible, évitez-le - en particulier, je vous suggère d'éviter d'imbriquer les instructions With.

C # 3.0 a cette fonctionnalité uniquement pour l'initialisation d'objet:

var x = new Whatever { PropertyA=true, PropertyB="Inactive" };

Non seulement cela est à peu près nécessaire pour LINQ, mais cela a également un sens en termes de où la syntaxe n'indique pas une odeur de code. Je trouve généralement que lorsque j'exécute de nombreuses opérations différentes sur un objet au-delà de sa construction initiale, ces opérations doivent être encapsulées comme une seule sur l'objet lui-même.

Une remarque sur votre exemple - avez-vous vraiment besoin du "Moi"? Pourquoi ne pas simplement écrire:

PropertyA = True
PropertyB = "Inactive"

? "Moi" est certainement impliqué dans ce cas ...

6
Jon Skeet

Je me méfierais du code qui utilise beaucoup ce mot-clé: s'il est utilisé pour faciliter la définition de nombreuses variables ou propriétés d'instance, je pense que cela peut indiquer que vos classes sont trop grandes ( Large Class smell ). Si vous l'utilisez pour remplacer de longues chaînes d'appels comme ceci:

UserHandler.GetUser.First.User.FirstName="Stefan"
UserHandler.GetUser.First.User.LastName="Karlsson"
UserHandler.GetUser.First.User.Age="39"
UserHandler.GetUser.First.User.Sex="Male"
UserHandler.GetUser.First.User.Occupation="Programmer"
UserHandler.GetUser.First.User.UserID="0"

alors vous violez probablement Loi Demeter

5
ljorquera

Je n'utilise pas VB.NET (j'avais l'habitude d'utiliser du VB simple) mais ...

Le premier point est-il obligatoire? Si c'est le cas, je ne vois aucun problème. En Javascript, le résultat de l'utilisation de with est qu'une propriété d'un objet ressemble exactement à une variable ordinaire, et que est très dangereux, car vous ne voyez pas si vous 'accède à une propriété ou une variable, et donc, with est quelque chose à éviter.

Non seulement son utilisation est plus facile pour les yeux, mais pour un accès répété aux propriétés d'un objet, il est susceptible d'être plus rapide, car l'objet n'est récupéré dans la chaîne de méthodes qu'une seule fois, et pas une fois pour chaque propriété.

Je suis d'accord avec d'autres réponses que vous devez éviter l'utilisation imbriquée de with, pour la même raison que pour éviter with tout à fait en Javascript: parce que vous ne voyez plus à quel objet votre propriété appartient .

3
bart

Le "avec" est fondamentalement la "cascade" de Smalltalk. C'est un modèle du livre Smalltalk Best Practice Patterns de Kent Beck.

Un résumé du modèle: utilisez-le quand il est logique de regrouper les messages envoyés à l'objet. Ne l'utilisez pas s'il se trouve que certains messages sont envoyés au même objet.

3
soemirno

ÉVITEZ le AVEC bloc à tout prix (même lisibilité). Deux raisons:

  1. Documentation Microsoft sur With ... End With indique que dans certaines circonstances, il crée une copie des données sur la pile, de sorte que toutes les modifications que vous apportez seront supprimées.
  2. Si vous l'utilisez pour des requêtes LINQ, les résultats lambda NE PAS chaîner et donc le résultat de chaque clause intermédiaire est jeté.

Pour décrire cela, nous avons un exemple (cassé) d'un manuel que mon collègue a dû demander à l'auteur (c'est en effet incorrect, les noms ont été modifiés pour protéger ... peu importe):

Avec dbcontext.Blahs
. OrderBy (Fonction (currentBlah) currentBlah.LastName)
. ThenBy (Fonction (currentBlah) currentBlah.FirstName)
.Charge()
Terminer par

Les commandes OrderBy et ThenBy n'ont aucun effet . SI vous reformatez le code en supprimant UNIQUEMENT le With et End With, et en ajoutant des caractères de continuation de ligne à la fin des trois premières lignes ... cela fonctionne (comme indiqué 15 pages plus tard de la même manière cahier de texte).

Nous n'avons plus besoin de raison pour chercher et détruire AVEC des blocs. Ils n'avaient de sens que dans un cadre interprété .

2
user4624979

Il y a un problème lorsque vous l'utilisez avec des structures, c'est-à-dire que vous ne pouvez pas définir leurs champs, car vous travaillez sur une copie locale (faite au moment de l'entrée dans avec le bloc) de l'expression "avec" et ne travaillez pas avec une (copie d'une) référence d'objet dans ce cas:

Le type de données objectExpression peut être n'importe quel type de classe ou de structure ou même un type élémentaire Visual Basic tel que Integer. Si objectExpression produit autre chose qu'un objet, vous pouvez uniquement lire les valeurs de ses membres ou appeler des méthodes, et vous obtenez une erreur si vous essayez d'affecter des valeurs aux membres d'une structure utilisée dans une instruction With ... End With. Il s'agit de la même erreur que vous obtiendriez si vous appeliez une méthode qui renvoyait une structure et accédait immédiatement et attribuait une valeur à un membre du résultat de la fonction, comme GetAPoint (). X = 1. Le problème dans les deux cas est que le la structure n'existe que sur la pile des appels et il n'y a aucun moyen qu'un membre de structure modifié dans ces situations puisse écrire dans un emplacement tel que tout autre code du programme puisse observer le changement.

L'objetExpression est évalué une fois, lors de l'entrée dans le bloc. Vous ne pouvez pas réaffecter objectExpression à partir du bloc With.

https://docs.Microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/with-end-with-statement

suppose que le compilateur aurait pu être un peu plus intelligent si vous passiez à l'instruction un nom de structure au lieu d'une expression qui renvoie une structure, mais il semble que ce ne soit pas

1
George Birbilis