Comment générer un constructeur à partir de champs de classe à l'aide de Visual Studio (et / ou ReSharper)?


159

Je me suis habitué à de nombreux IDE Java ( Eclipse , NetBeans et IntelliJ IDEA ) qui vous fournissent une commande pour générer un constructeur par défaut pour une classe en fonction des champs de la classe.

Par exemple:

public class Example
{
    public decimal MyNumber { get; set; }
    public string Description { get; set; }
    public int SomeInteger { get; set; }

    // ↓↓↓ This is what I want generated ↓↓↓
    public Example(decimal myNumber, string description, int someInteger)
    {
        MyNumber = myNumber;
        Description = description;
        SomeInteger = someInteger;
    }
}

Faire remplir tous les champs d'un objet par un constructeur est une tâche si courante dans la plupart des langages POO, je suppose qu'il existe un moyen pour moi de gagner du temps en écrivant ce code standard en C #. Je suis nouveau dans le monde C #, alors je me demande si je manque quelque chose de fondamental dans le langage? Existe-t-il une option dans Visual Studio qui est évidente?

Réponses:


124

ReSharper propose un outil Generate Constructor dans lequel vous pouvez sélectionner n'importe quel champ / propriétés que vous souhaitez initialiser. J'utilise le raccourci clavier Alt+ Inspour y accéder.


Cela répond à la question pour moi en termes de "réalisation". Cependant, il n'y a pas de support pour cela directement dans VS2010, non?
Elijah le

1
Comme Jared le mentionne ci-dessous, VS2010 a ajouté un outil "Générer à partir de l'utilisation", mais pour autant que je sache, il n'y a aucun moyen de générer un constructeur basé sur des champs qui sont déjà dans la classe. Si vous essayez d'instancier la classe avec une signature qui ne correspond à aucune de celles existantes, il vous proposera de générer ce constructeur pour vous.
James Kolpack

Oh wow, je sais que c'est une question assez ancienne mais je viens juste de la découvrir!
Brett

49
Vous devriez probablement mentionner que ReSharper n'est pas gratuit .
b1nary.atr0phy

184

Dans Visual Studio 2015 Update3, j'ai cette fonctionnalité.

Juste en mettant en surbrillance les propriétés, puis appuyez sur Ctrl+ ., puis appuyez sur Générer le constructeur .

Par exemple, si vous avez mis en évidence deux propriétés, il vous suggérera de créer un constructeur avec deux paramètres et si vous en avez sélectionné trois, il en suggérera un avec trois paramètres et ainsi de suite.

Il fonctionne également avec Visual Studio 2017.

Génération automatique de la visualisation des raccourcis


3
Hé, cela a fonctionné pour moi dans la communauté Visual Studio 2015. Je ne sais pas comment cela n'est pas très connu du public, mais c'est bien. Merci. :)
Le 0bserver

3
C'est parfait. Le travail que cela aurait pu sauver si je l'avais lu le jour où vous l'avez posté ... xD
Timo

3
Pour ce que ça vaut, la fonctionnalité n'apparaît pas si vous utilisez les propriétés en lecture seule C # 6. (par exemple, public int Age { get; }) Ils doivent avoir un paramètre spécifié, même temporairement, pour que l'option soit disponible. Testé dans la communauté VS2015; Je ne sais pas si cela a été corrigé dans VS2017.
Chris Sinclair

1
@PouyaSamie: En C # 6.0, les propriétés automatiques en lecture seule peuvent être affectées dans le constructeur. Voir ceci pour un exemple: github.com/dotnet/roslyn/wiki/…
Chris Sinclair

5
C'est la solution parfaite! Je marquerais ceci comme la vraie solution!
Václav Holuša

29

C # a ajouté une nouvelle fonctionnalité dans Visual Studio 2010 appelée générer à partir de l'utilisation. L'intention est de générer le code standard à partir d'un modèle d'utilisation. L'une des fonctionnalités génère un constructeur basé sur un modèle d'initialisation.

La fonction est accessible via la balise active qui apparaîtra lorsque le motif est détecté.

Par exemple, disons que j'ai la classe suivante

class MyType { 

}

Et j'écris ce qui suit dans mon application

var v1 = new MyType(42);

Un constructeur prenant un intn'existe pas, donc une balise active apparaîtra et l'une des options sera "Générer le stub du constructeur". La sélection de cela modifiera le code pour MyTypeêtre le suivant.

class MyType {
    private int p;
    public MyType(int p) {
        // TODO: Complete member initialization
        this.p = p;
    }
}

15

Vous pouvez écrire une macro pour ce faire - vous utiliseriez l'analyseur de Visual Studio pour récupérer des informations sur les membres de la classe.

J'ai écrit une macro similaire. (Je vais partager le code ci-dessous). La macro que j'ai écrite est pour copier en avant tous les constructeurs d'une classe de base lorsque vous en héritez (utile pour les classes comme Exception qui ont beaucoup de surcharges sur le ctor).

Voici ma macro (encore une fois, cela ne résout pas votre problème, mais vous pouvez probablement modifier pour faire ce que vous voulez)


Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports EnvDTE100
Imports System.Diagnostics

Public Module ConstructorEditor
    Public Sub StubConstructors()
        'adds stubs for all of the constructors in the current class's base class
        Dim selection As TextSelection = DTE.ActiveDocument.Selection
        Dim classInfo As CodeClass2 = GetClassElement()

        If classInfo Is Nothing Then
            System.Windows.Forms.MessageBox.Show("No class was found surrounding the cursor.  Make sure that this file compiles and try again.", "Error")
            Return
        End If

        If classInfo.Bases.Count = 0 Then
            System.Windows.Forms.MessageBox.Show("No parent class was found for this class.  Make sure that this file, and any file containing parent classes compiles and try again")
            Return
        End If

        'setting up an undo context -- one ctrl+z undoes everything
        Dim closeUndoContext As Boolean = False
        If DTE.UndoContext.IsOpen = False Then
            closeUndoContext = True
            DTE.UndoContext.Open("StubConstructorsContext", False)
        End If

        Try
            Dim parentInfo As CodeClass2 = classInfo.Bases.Item(1)
            Dim childConstructors As System.Collections.Generic.List(Of CodeFunction2) = GetConstructors(classInfo)
            Dim parentConstructors As System.Collections.Generic.List(Of CodeFunction2) = GetConstructors(parentInfo)
            For Each constructor As CodeFunction2 In parentConstructors
                If Not MatchingSignatureExists(constructor, childConstructors) Then
                    ' we only want to create ctor stubs for ctors that are missing
                    ' note: a dictionary could be more efficient, but I doubt most classes will have more than 4 or 5 ctors...
                    StubConstructor(classInfo, constructor)
                End If
            Next
        Finally
            If closeUndoContext Then
                DTE.UndoContext.Close()
            End If
        End Try
    End Sub
    Private Function GetConstructors(ByVal classInfo As CodeClass2) As System.Collections.Generic.List(Of CodeFunction2)
        ' return a list of all of the constructors in the specified class
        Dim result As System.Collections.Generic.List(Of CodeFunction2) = New System.Collections.Generic.List(Of CodeFunction2)
        Dim func As CodeFunction2
        For Each member As CodeElement2 In classInfo.Members
            ' members collection has all class members.  filter out just the function members, and then of the functions, grab just the ctors
            func = TryCast(member, CodeFunction2)
            If func Is Nothing Then Continue For
            If func.FunctionKind = vsCMFunction.vsCMFunctionConstructor Then
                result.Add(func)
            End If
        Next
        Return result
    End Function
    Private Function MatchingSignatureExists(ByVal searchFunction As CodeFunction2, ByVal functions As System.Collections.Generic.List(Of CodeFunction2)) As Boolean
        ' given a function (searchFunction), searches a list of functions where the function signatures (not necessarily the names) match
        ' return null if no match is found, otherwise returns first match
        For Each func As CodeFunction In functions
            If func.Parameters.Count <> searchFunction.Parameters.Count Then Continue For
            Dim searchParam As CodeParameter2
            Dim funcParam As CodeParameter2
            Dim match As Boolean = True

            For count As Integer = 1 To searchFunction.Parameters.Count
                searchParam = searchFunction.Parameters.Item(count)
                funcParam = func.Parameters.Item(count)
                If searchParam.Type.AsFullName <> funcParam.Type.AsFullName Then
                    match = False
                    Exit For
                End If
            Next

            If match Then
                Return True
            End If
        Next
        ' no match found
        Return False
    End Function

    Private Sub StubConstructor(ByVal classInfo As CodeClass2, ByVal parentConstructor As CodeFunction2)
        ' adds a constructor to the current class, based upon the parentConstructor that is passed in

        ' highly inefficient hack to position the ctor where I want it (after the last ctor in the class, if there is another ctor
        ' note that passing zero as the position (put the ctor first) caused some problems when we were adding ctors to classes that already had ctors
        Dim position As Object
        Dim ctors As System.Collections.Generic.List(Of CodeFunction2) = GetConstructors(classInfo)

        If ctors.Count = 0 Then
            position = 0
        Else
            position = ctors.Item(ctors.Count - 1)
        End If

        ' if there are no other ctors, put this one at the top
        Dim ctor As CodeFunction2 = classInfo.AddFunction(classInfo.Name, vsCMFunction.vsCMFunctionConstructor, vsCMTypeRef.vsCMTypeRefVoid, position, parentConstructor.Access)

        Dim baseCall As String = ":base("
        Dim separator As String = ""
        For Each parameter As CodeParameter2 In parentConstructor.Parameters
            ctor.AddParameter(parameter.Name, parameter.Type, -1)
            baseCall += separator + parameter.Name
            separator = ", "
        Next
        baseCall += ")"

        ' and 1 sad hack -- appears to be no way to programmatically add the :base() calls without using direct string manipulation
        Dim startPoint As TextPoint = ctor.GetStartPoint()
        Dim endOfSignature As EditPoint = startPoint.CreateEditPoint()
        endOfSignature.EndOfLine()
        endOfSignature.Insert(baseCall)
        startPoint.CreateEditPoint().SmartFormat(endOfSignature)
    End Sub

    Private Function GetClassElement() As CodeClass2
        'returns a CodeClass2 element representing the class that the cursor is within, or null if there is no class
        Try
            Dim selection As TextSelection = DTE.ActiveDocument.Selection
            Dim fileCodeModel As FileCodeModel2 = DTE.ActiveDocument.ProjectItem.FileCodeModel
            Dim element As CodeElement2 = fileCodeModel.CodeElementFromPoint(selection.TopPoint, vsCMElement.vsCMElementClass)
            Return element
        Catch
            Return Nothing
        End Try
    End Function

End Module


1
Il manque un opérateur: "If searchParam.Type.AsFullName funcParam.Type.AsFullName Then" devrait être "If searchParam.Type.AsFullName = funcParam.Type.AsFullName Then"
LTR

1
@LTR Great catch - sauf qu'il est censé être "If searchParam.Type.AsFullName <> funcParam.Type.AsFullName". J'ai raté l'échappement sur les crochets angulaires - ils sont apparus dans l'éditeur, mais pas dans la vue. Merci!
JMarsch

13

À partir de Visual Studio 2017, cela semble être une fonctionnalité intégrée. Appuyez sur Ctrl+ .pendant que votre curseur est dans le corps de la classe, et sélectionnez "Générer un constructeur" dans la liste déroulante Actions rapides et refactorisations .


11

Voici une macro que j'utilise à cet effet. Il générera un constructeur à partir des champs et des propriétés qui ont un setter privé.

Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports EnvDTE90a
Imports EnvDTE100
Imports System.Diagnostics
Imports System.Collections.Generic

Public Module Temp

    Sub AddConstructorFromFields()
        DTE.UndoContext.Open("Add constructor from fields")

        Dim classElement As CodeClass, index As Integer
        GetClassAndInsertionIndex(classElement, index)

        Dim constructor As CodeFunction
        constructor = classElement.AddFunction(classElement.Name, vsCMFunction.vsCMFunctionConstructor, vsCMTypeRef.vsCMTypeRefVoid, index, vsCMAccess.vsCMAccessPublic)

        Dim visitedNames As New Dictionary(Of String, String)
        Dim element As CodeElement, parameterPosition As Integer, isFirst As Boolean = True
        For Each element In classElement.Children
            Dim fieldType As String
            Dim fieldName As String
            Dim parameterName As String

            Select Case element.Kind
                Case vsCMElement.vsCMElementVariable
                    Dim field As CodeVariable = CType(element, CodeVariable)
                    fieldType = field.Type.AsString
                    fieldName = field.Name
                    parameterName = field.Name.TrimStart("_".ToCharArray())

                Case vsCMElement.vsCMElementProperty
                    Dim field As CodeProperty = CType(element, CodeProperty)
                    If field.Setter.Access = vsCMAccess.vsCMAccessPrivate Then
                        fieldType = field.Type.AsString
                        fieldName = field.Name
                        parameterName = field.Name.Substring(0, 1).ToLower() + field.Name.Substring(1)
                    End If
            End Select

            If Not String.IsNullOrEmpty(parameterName) And Not visitedNames.ContainsKey(parameterName) Then
                visitedNames.Add(parameterName, parameterName)

                constructor.AddParameter(parameterName, fieldType, parameterPosition)

                Dim endPoint As EditPoint
                endPoint = constructor.EndPoint.CreateEditPoint()
                endPoint.LineUp()
                endPoint.EndOfLine()

                If Not isFirst Then
                    endPoint.Insert(Environment.NewLine)
                Else
                    isFirst = False
                End If

                endPoint.Insert(String.Format(MemberAssignmentFormat(constructor.Language), fieldName, parameterName))

                parameterPosition = parameterPosition + 1
            End If
        Next

        DTE.UndoContext.Close()

        Try
            ' This command fails sometimes '
            DTE.ExecuteCommand("Edit.FormatDocument")
        Catch ex As Exception
        End Try
    End Sub
    Private Sub GetClassAndInsertionIndex(ByRef classElement As CodeClass, ByRef index As Integer, Optional ByVal useStartIndex As Boolean = False)
        Dim selection As TextSelection
        selection = CType(DTE.ActiveDocument.Selection, TextSelection)

        classElement = CType(selection.ActivePoint.CodeElement(vsCMElement.vsCMElementClass), CodeClass)

        Dim childElement As CodeElement
        index = 0
        For Each childElement In classElement.Children
            Dim childOffset As Integer
            childOffset = childElement.GetStartPoint(vsCMPart.vsCMPartWholeWithAttributes).AbsoluteCharOffset
            If selection.ActivePoint.AbsoluteCharOffset < childOffset Or useStartIndex Then
                Exit For
            End If
            index = index + 1
        Next
    End Sub
    Private ReadOnly Property MemberAssignmentFormat(ByVal language As String) As String
        Get
            Select Case language
                Case CodeModelLanguageConstants.vsCMLanguageCSharp
                    Return "this.{0} = {1};"

                Case CodeModelLanguageConstants.vsCMLanguageVB
                    Return "Me.{0} = {1}"

                Case Else
                    Return ""
            End Select
        End Get
    End Property
End Module

J'ai dû diviser la ligne: "If Not String.IsNullOrEmpty (parameterName) And Not viewedNames.ContainsKey (parameterName) Then" en deux lignes pour éviter une exception de référence nulle:
cedd

9

Peut-être que vous pourriez essayer ceci: http://cometaddin.codeplex.com/


CodePlex a été fermé (mais le lien est actuellement encore quelque peu valide, avec une archive téléchargeable). Mais essayez peut-être de mettre à jour le lien (si le projet a été déplacé ailleurs). Et / ou prendre des mesures pour éviter un désastre si le lien actuel est rompu à l'avenir.
Peter Mortensen

5

Vous pouvez le faire facilement avec ReSharper 8 ou version ultérieure. La ctorf, ctorpet des ctorfpextraits génèrent des constructeurs qui peuplent tous les champs, les propriétés ou les champs et les propriétés d'une classe.


4

Voici la macro Visual Studio de JMarsh modifiée pour générer un constructeur basé sur les champs et les propriétés de la classe.

Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports EnvDTE100
Imports System.Diagnostics
Imports System.Collections.Generic

Public Module ConstructorEditor

    Public Sub AddConstructorFromFields()

        Dim classInfo As CodeClass2 = GetClassElement()
        If classInfo Is Nothing Then
            System.Windows.Forms.MessageBox.Show("No class was found surrounding the cursor.  Make sure that this file compiles and try again.", "Error")
            Return
        End If

        ' Setting up undo context. One Ctrl+Z undoes everything
        Dim closeUndoContext As Boolean = False
        If DTE.UndoContext.IsOpen = False Then
            closeUndoContext = True
            DTE.UndoContext.Open("AddConstructorFromFields", False)
        End If

        Try
            Dim dataMembers As List(Of DataMember) = GetDataMembers(classInfo)
            AddConstructor(classInfo, dataMembers)
        Finally
            If closeUndoContext Then
                DTE.UndoContext.Close()
            End If
        End Try

    End Sub

    Private Function GetClassElement() As CodeClass2
        ' Returns a CodeClass2 element representing the class that the cursor is within, or null if there is no class
        Try
            Dim selection As TextSelection = DTE.ActiveDocument.Selection
            Dim fileCodeModel As FileCodeModel2 = DTE.ActiveDocument.ProjectItem.FileCodeModel
            Dim element As CodeElement2 = fileCodeModel.CodeElementFromPoint(selection.TopPoint, vsCMElement.vsCMElementClass)
            Return element
        Catch
            Return Nothing
        End Try
    End Function

    Private Function GetDataMembers(ByVal classInfo As CodeClass2) As System.Collections.Generic.List(Of DataMember)

        Dim dataMembers As List(Of DataMember) = New List(Of DataMember)
        Dim prop As CodeProperty2
        Dim v As CodeVariable2

        For Each member As CodeElement2 In classInfo.Members

            prop = TryCast(member, CodeProperty2)
            If Not prop Is Nothing Then
                dataMembers.Add(DataMember.FromProperty(prop.Name, prop.Type))
            End If

            v = TryCast(member, CodeVariable2)
            If Not v Is Nothing Then
                If v.Name.StartsWith("_") And Not v.IsConstant Then
                    dataMembers.Add(DataMember.FromPrivateVariable(v.Name, v.Type))
                End If
            End If

        Next

        Return dataMembers

    End Function

    Private Sub AddConstructor(ByVal classInfo As CodeClass2, ByVal dataMembers As List(Of DataMember))

        ' Put constructor after the data members
        Dim position As Object = dataMembers.Count

        ' Add new constructor
        Dim ctor As CodeFunction2 = classInfo.AddFunction(classInfo.Name, vsCMFunction.vsCMFunctionConstructor, vsCMTypeRef.vsCMTypeRefVoid, position, vsCMAccess.vsCMAccessPublic)

        For Each dataMember As DataMember In dataMembers
            ctor.AddParameter(dataMember.NameLocal, dataMember.Type, -1)
        Next

        ' Assignments
        Dim startPoint As TextPoint = ctor.GetStartPoint(vsCMPart.vsCMPartBody)
        Dim point As EditPoint = startPoint.CreateEditPoint()
        For Each dataMember As DataMember In dataMembers
            point.Insert("            " + dataMember.Name + " = " + dataMember.NameLocal + ";" + Environment.NewLine)
        Next

    End Sub

    Class DataMember

        Public Name As String
        Public NameLocal As String
        Public Type As Object

        Private Sub New(ByVal name As String, ByVal nameLocal As String, ByVal type As Object)
            Me.Name = name
            Me.NameLocal = nameLocal
            Me.Type = type
        End Sub

        Shared Function FromProperty(ByVal name As String, ByVal type As Object)

            Dim nameLocal As String
            If Len(name) > 1 Then
                nameLocal = name.Substring(0, 1).ToLower + name.Substring(1)
            Else
                nameLocal = name.ToLower()
            End If

            Return New DataMember(name, nameLocal, type)

        End Function

        Shared Function FromPrivateVariable(ByVal name As String, ByVal type As Object)

            If Not name.StartsWith("_") Then
                Throw New ArgumentException("Expected private variable name to start with underscore.")
            End If

            Dim nameLocal As String = name.Substring(1)

            Return New DataMember(name, nameLocal, type)

        End Function

    End Class

End Module

2

Pour Visual Studio 2015, j'ai trouvé une extension qui fait exactement cela. Il semble bien fonctionner et le nombre de téléchargements est raisonnablement élevé. Donc, si vous ne pouvez pas ou ne voulez pas utiliser ReSharper, vous pouvez installer celui-ci à la place.

Vous pouvez également l'acquérir via NuGet .


-3

J'utilise l'astuce suivante:

Je sélectionne la déclaration de la classe avec les données-membres et appuie sur:

Ctrl+ C, Shift+ Ctrl+ C, Ctrl+ V.

  • La première commande copie la déclaration dans le presse-papiers,
  • La deuxième commande est un raccourci qui appelle le PROGRAM
  • La dernière commande écrase la sélection par le texte du presse-papiers.

Le PROGRAM obtient la déclaration du presse-papiers, trouve le nom de la classe, trouve tous les membres et leurs types, génère le constructeur et le copie tout dans le presse-papiers.

Nous le faisons avec des étudiants de première année sur ma pratique "Programming-I" (Université Charles, Prague) et la plupart des étudiants le font jusqu'à la fin de l'heure.

Si vous voulez voir le code source, faites-le moi savoir.


1
La deuxième commande est un raccourci vers la vue de classe, n'est-ce pas? Ou cette astuce ne concerne-t-elle pas Visual Studio 2010?
Joel Peltonen
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.