Wie generiere ich einen Konstruktor aus Klassenfeldern mit Visual Studio (und / oder ReSharper)?

158

Ich habe mich an viele Java-IDEs ( Eclipse , NetBeans und IntelliJ IDEA) gewöhnt ) mit denen Sie einen Befehl zum Generieren eines Standardkonstruktors für eine Klasse basierend auf den Feldern in der Klasse erhalten.

Beispielsweise:

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;
    }
}

Wenn ein Konstruktor alle Felder eines Objekts ausfüllt, ist dies in den meisten OOP-Sprachen eine so häufige Aufgabe. Ich gehe davon aus, dass ich auf diese Weise Zeit sparen kann, wenn ich diesen Code in C # schreibe. Ich bin neu in der C # -Welt und frage mich, ob mir etwas Grundlegendes an der Sprache fehlt. Gibt es in Visual Studio eine Option, die offensichtlich ist?

Elijah
quelle

Antworten:

124

ReSharper bietet ein Generate Constructor- Tool, mit dem Sie alle Felder / Eigenschaften auswählen können, die Sie initialisieren möchten. Ich benutze den AltHotkey +, Insum darauf zuzugreifen.

James Kolpack
quelle
Das beantwortet die Frage für mich in Bezug auf "es schaffen". Es gibt jedoch keine Unterstützung dafür in VS2010 direkt, oder?
Elijah
1
Wie Jared weiter unten erwähnt, hat VS2010 ein Tool "Aus Verwendung generieren" hinzugefügt, aber soweit ich das beurteilen kann, gibt es keine Möglichkeit, einen Konstruktor basierend auf Feldern zu generieren, die sich bereits in der Klasse befinden. Wenn Sie versuchen, die Klasse mit einer Signatur zu instanziieren, die keiner vorhandenen entspricht, wird angeboten, diesen Konstruktor für Sie zu generieren.
James Kolpack
Oh wow, ich weiß, dass dies eine ziemlich alte Frage ist, aber ich habe das gerade erst entdeckt!
Brett
49
Sie sollten wahrscheinlich erwähnen, dass ReSharper nicht kostenlos ist .
b1nary.atr0phy
184

In Visual Studio 2015 Update3 habe ich diese Funktion.

Markieren Sie einfach die Eigenschaften und drücken Sie dann Ctrl+ .und dann Generate Constructor .

Wenn Sie beispielsweise zwei Eigenschaften hervorgehoben haben, wird vorgeschlagen, einen Konstruktor mit zwei Parametern zu erstellen. Wenn Sie drei ausgewählt haben, wird einer mit drei Parametern usw. vorgeschlagen.

Es funktioniert auch mit Visual Studio 2017.

Verknüpfungsvisualisierung automatisch generieren

Pouya Samie
quelle
3
Hey, das hat bei mir in der Visual Studio 2015-Community funktioniert. Ich bin mir nicht sicher, wie das nicht sehr öffentlich bekannt ist, aber das ist schön. Vielen Dank. :)
Der 0bserver
3
Das ist perfekt. Die Arbeit, die dies hätte retten können, wenn ich sie an dem Tag gelesen hätte, an dem Sie sie veröffentlicht haben ... xD
Timo
3
Für das, was es wert ist, wird die Funktion nicht angezeigt, wenn Sie schreibgeschützte C # 6-Eigenschaften verwenden. (z. B. public int Age { get; }) Sie müssen bei Setzern angegeben haben, auch wenn dies vorübergehend ist, damit die Option verfügbar ist. Getestet in der VS2015 Community; Ich bin mir nicht sicher, ob dies in VS2017 behoben wurde.
Chris Sinclair
1
@PouyaSamie: In C # 6.0 können im Konstruktor schreibgeschützte Autoeigenschaften zugewiesen werden. Ein Beispiel finden Sie hier: github.com/dotnet/roslyn/wiki/…
Chris Sinclair
5
Das ist die perfekte Lösung! Ich würde dies als die echte Lösung markieren!
Václav Holuša
29

C # hat in Visual Studio 2010 eine neue Funktion namens "Aus Verwendung generieren" hinzugefügt. Die Absicht ist, den Standardcode aus einem Verwendungsmuster zu generieren. Eines der Merkmale ist das Generieren eines Konstruktors basierend auf einem Initialisierungsmuster.

Auf die Funktion kann über das Smart Tag zugegriffen werden, das angezeigt wird, wenn das Muster erkannt wird.

Nehmen wir zum Beispiel an, ich habe die folgende Klasse

class MyType { 

}

Und ich schreibe folgendes in meine Bewerbung

var v1 = new MyType(42);

Ein Konstruktor, der ein intnimmt, existiert nicht, daher wird ein Smart-Tag angezeigt und eine der Optionen ist "Konstruktor-Stub generieren". Wenn Sie dies auswählen, wird der Code MyTypewie folgt geändert .

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

Sie könnten dazu ein Makro schreiben - Sie würden den Parser von Visual Studio verwenden, um Informationen über die Mitglieder der Klasse abzurufen.

Ich habe ein ähnliches Makro geschrieben. (Ich werde den Code unten teilen). Das Makro, das ich geschrieben habe, dient zum Vorwärtskopieren aller Konstruktoren in einer Basisklasse, wenn Sie davon erben (nützlich für Klassen wie Exception, die viele ctor-Überladungen aufweisen).

Hier ist mein Makro (wieder löst es Ihr Problem nicht, aber Sie können es wahrscheinlich ändern, um das zu tun, was Sie wollen)


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

JMarsch
quelle
1
Es fehlt ein Operator: "Wenn searchParam.Type.AsFullName funcParam.Type.AsFullName Then" sollte "If searchParam.Type.AsFullName = funcParam.Type.AsFullName Then" sein
LTR
1
@LTR Großartiger Fang - außer es sollte "If searchParam.Type.AsFullName <> funcParam.Type.AsFullName" sein. Ich habe die Flucht in den spitzen Klammern verpasst - sie erschienen im Editor, aber nicht in der Ansicht. Vielen Dank!
JMarsch
13

Ab Visual Studio 2017 scheint dies eine integrierte Funktion zu sein. Drücken Sie Ctrl+, .während sich Ihr Cursor im Klassenkörper befindet, und wählen Sie "Konstruktor generieren" aus der Dropdown-Liste " Schnelle Aktionen und Refactorings ".

lt1
quelle
11

Hier ist ein Makro, das ich für diesen Zweck verwende. Es wird ein Konstruktor aus Feldern und Eigenschaften generiert, die einen privaten Setter haben.

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
Antoine Aubry
quelle
Ich musste die Zeile "Wenn nicht String.IsNullOrEmpty (Parametername) und Nicht besuchteNames.ContainsKey (Parametername) Dann" in zwei Zeilen
aufteilen
9

Vielleicht könnten Sie dies ausprobieren: http://cometaddin.codeplex.com/

Simon
quelle
CodePlex wurde heruntergefahren (aber der Link ist derzeit noch gültig, mit einem herunterladbaren Archiv). Versuchen Sie aber möglicherweise, den Link zu aktualisieren (wenn das Projekt an einen anderen Ort verschoben wurde). Und / oder Maßnahmen ergreifen, um eine Katastrophe zu verhindern, wenn die aktuelle Verbindung in Zukunft unterbrochen wird.
Peter Mortensen
5

Sie können dies problemlos mit ReSharper 8 oder höher tun. Das ctorf, ctorpund ctorfpSchnipsel generieren Konstrukteuren , die alle Felder, Eigenschaften bevölkern , oder Felder und Eigenschaften einer Klasse.

Mike B.
quelle
4

Hier ist das Visual Studio-Makro von JMarsh , das geändert wurde, um einen Konstruktor basierend auf den Feldern und Eigenschaften in der Klasse zu generieren.

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
rybo103
quelle
2

Für Visual Studio 2015 habe ich eine Erweiterung gefunden , die genau dies tut. Es scheint gut zu funktionieren und hat eine relativ hohe Anzahl an Downloads. Wenn Sie ReSharper nicht verwenden können oder möchten, können Sie stattdessen ReSharper installieren.

Sie können es auch über NuGet erwerben .

TKharaishvili
quelle
-3

Ich benutze den folgenden Trick:

Ich wähle die Deklaration der Klasse mit den Datenelementen aus und drücke:

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

  • Der erste Befehl kopiert die Deklaration in die Zwischenablage.
  • Der zweite Befehl ist eine Verknüpfung, die das PROGRAMM aufruft
  • Der letzte Befehl überschreibt die Auswahl durch Text aus der Zwischenablage.

Das PROGRAMM ruft die Deklaration aus der Zwischenablage ab, findet den Namen der Klasse, findet alle Mitglieder und ihre Typen, generiert einen Konstruktor und kopiert alles zurück in die Zwischenablage.

Wir machen es mit Neulingen in meiner "Programming-I" -Praxis (Karlsuniversität, Prag) und die meisten Studenten schaffen es bis zum Ende der Stunde.

Wenn Sie den Quellcode sehen möchten, lassen Sie es mich wissen.

Tom Holan
quelle
1
Der zweite Befehl ist eine Verknüpfung zur Klassenansicht, nicht wahr? Oder handelt dieser Tipp nicht von Visual Studio 2010?
Joel Peltonen