Bagaimana cara menghasilkan konstruktor dari bidang kelas menggunakan Visual Studio (dan / atau ReSharper)?

159

Saya sudah terbiasa dengan banyak IDE Java ( Eclipse , NetBeans , dan IntelliJ IDEA ) yang memberi Anda perintah untuk membuat konstruktor default untuk kelas berdasarkan bidang di kelas.

Sebagai contoh:

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

Memiliki konstruktor mengisi semua bidang objek adalah tugas yang umum di sebagian besar bahasa OOP, saya berasumsi bahwa ada beberapa cara bagi saya untuk menghemat waktu menulis kode boilerplate ini dalam C #. Saya baru di dunia C #, jadi saya ingin tahu apakah saya kehilangan sesuatu yang mendasar tentang bahasa ini? Apakah ada beberapa opsi di Visual Studio yang jelas?

Elia
sumber

Jawaban:

124

ReSharper menawarkan alat Pembuat Konstruktor di mana Anda dapat memilih bidang / properti apa pun yang ingin Anda inisialisasi. Saya menggunakan Alt+ Inshot-key untuk mengakses ini.

James Kolpack
sumber
Itu menjawab pertanyaan untuk saya dalam hal "menyelesaikannya." Namun, tidak ada dukungan untuk itu di VS2010 secara langsung, bukan?
Elia
1
Seperti yang disebutkan Jared di bawah ini, VS2010 menambahkan alat "Hasilkan dari penggunaan", tetapi sejauh yang saya tahu, tidak ada cara untuk menghasilkan konstruktor berdasarkan bidang yang sudah ada di kelas. Jika Anda mencoba untuk membuat instance kelas dengan tanda tangan yang tidak cocok dengan yang ada, itu akan menawarkan untuk menghasilkan konstruktor untuk Anda.
James Kolpack
Oh wow, saya tahu ini pertanyaan yang cukup lama tapi saya baru saja menemukan ini!
Brett
49
Anda mungkin harus menyebutkan ReSharper tidak gratis .
b1nary.atr0phy
184

Di Visual Studio 2015 Update3 saya memiliki fitur ini.

Hanya dengan menyorot properti dan kemudian tekan Ctrl+ .dan kemudian tekan Hasilkan Konstruktor .

Misalnya, jika Anda telah menyoroti dua properti, itu akan menyarankan Anda untuk membuat konstruktor dengan dua parameter dan jika Anda memilih tiga itu akan menyarankan satu dengan tiga parameter dan seterusnya.

Ini juga berfungsi dengan Visual Studio 2017.

Buat visualisasi pintasan secara otomatis

Pouya Samie
sumber
3
Hei, ini berhasil bagi saya di komunitas Visual Studio 2015. Tidak yakin bagaimana ini tidak diketahui publik, tetapi ini bagus. Terima kasih. :)
The 0bserver
3
Itu sempurna. Karya ini bisa disimpan jika saya membacanya pada hari Anda mempostingnya ... xD
Timo
3
Betapapun baiknya, fitur ini tidak muncul jika Anda menggunakan properti hanya-baca C # 6. (misalnya, public int Age { get; }) Mereka harus memiliki setter yang ditentukan, bahkan jika sementara, untuk opsi yang tersedia. Diuji di Komunitas VS2015; tidak yakin apakah ini telah diperbaiki di VS2017.
Chris Sinclair
1
@PouyaSamie: Di C # 6.0, read-only auto-properties dapat ditugaskan di konstruktor Lihat ini sebagai contoh: github.com/dotnet/roslyn/wiki/…
Chris Sinclair
5
Ini solusi sempurna! Saya akan menandai ini solusi nyata!
Václav Holuša
29

C # menambahkan fitur baru di Visual Studio 2010 yang disebut menghasilkan dari penggunaan. Tujuannya adalah untuk menghasilkan kode standar dari pola penggunaan. Salah satu fitur adalah menghasilkan konstruktor berdasarkan pola inisialisasi.

Fitur ini dapat diakses melalui tag cerdas yang akan muncul ketika pola terdeteksi.

Sebagai contoh, katakanlah saya memiliki kelas berikut

class MyType { 

}

Dan saya menulis yang berikut ini di aplikasi saya

var v1 = new MyType(42);

Konstruktor yang mengambil inttidak ada sehingga tag cerdas akan muncul dan salah satu opsi akan menjadi "Hasilkan rintisan konstruktor". Memilih yang akan mengubah kode untuk MyTypemenjadi yang berikut.

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

Anda bisa menulis makro untuk melakukan ini - Anda akan menggunakan parser Visual Studio untuk mengambil informasi tentang anggota kelas.

Saya menulis makro serupa. (Saya akan membagikan kode di bawah ini). Makro yang saya tulis adalah untuk menyalin maju semua konstruktor di kelas dasar ketika Anda mewarisinya (berguna untuk kelas seperti Pengecualian yang memiliki banyak kelebihan pada ctor).

Inilah makro saya (sekali lagi, ini tidak menyelesaikan masalah Anda, tetapi Anda mungkin dapat memodifikasi untuk melakukan apa yang Anda inginkan)


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
sumber
1
Ada operator yang hilang: "Jika searchParam.Type.AsFullName funcParam.Type.AsFullName Then" seharusnya "If searchParam.Type.AsFullName = funcParam.Type.AsFullName Then"
LTR
1
@ LTR Tangkapan hebat - kecuali seharusnya "Jika searchParam.Type.AsFullName <> funcParam.Type.AsFullName". Saya merindukan pelarian di kurung sudut - mereka muncul di editor, tetapi tidak dalam tampilan. Terima kasih!
JMarsch
13

Pada Visual Studio 2017, ini terlihat menjadi fitur bawaan. Tekan Ctrl+ .ketika kursor Anda berada di badan kelas, dan pilih "Hasilkan Konstruktor" dari dropdown Tindakan Cepat dan Refactorings .

lt1
sumber
11

Berikut ini adalah makro yang saya gunakan untuk tujuan itu. Ini akan menghasilkan konstruktor dari bidang dan properti yang memiliki setter pribadi.

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
sumber
Saya harus membagi baris: "If Not String.IsNullOrEmpty (parameterName) Dan Not visitedNames.ContainsKey (parameterName) Then" menjadi dua baris untuk menghindari pengecualian referensi nol:
cedd
9

Mungkin Anda bisa mencoba ini: http://cometaddin.codeplex.com/

Simon
sumber
CodePlex telah dimatikan (tetapi tautannya saat ini masih agak valid, dengan arsip yang dapat diunduh). Tapi mungkin coba perbarui tautannya (jika proyek telah dipindahkan ke tempat lain). Dan / atau mengambil langkah-langkah untuk mencegah bencana jika tautan saat ini rusak di masa depan.
Peter Mortensen
5

Anda dapat melakukan ini dengan mudah dengan ReSharper 8 atau lebih baru. The ctorf,, ctorpdan ctorfpcuplikan menghasilkan konstruktor yang mengisi semua bidang, properti, atau bidang dan properti kelas.

Mike B
sumber
4

Inilah makro Visual Studio JMarsh yang dimodifikasi untuk menghasilkan konstruktor berdasarkan bidang dan properti di kelas.

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
sumber
2

Untuk Visual Studio 2015 saya menemukan ekstensi yang melakukan hal ini. Tampaknya berfungsi dengan baik dan memiliki jumlah unduhan yang cukup tinggi. Jadi jika Anda tidak dapat atau tidak ingin menggunakan ReSharper, Anda dapat menginstal yang ini sebagai gantinya.

Anda juga dapat memperolehnya melalui NuGet .

TKharaishvili
sumber
-3

Saya menggunakan trik berikut:

Saya memilih deklarasi kelas dengan data-anggota dan tekan:

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

  • Perintah pertama menyalin deklarasi ke clipboard,
  • Perintah kedua adalah jalan pintas yang memanggil PROGRAM
  • Perintah terakhir menimpa pemilihan dengan teks dari clipboard.

PROGRAM mendapatkan deklarasi dari clipboard, menemukan nama kelas, menemukan semua anggota dan tipenya, menghasilkan konstruktor dan menyalinnya kembali ke clipboard.

Kami melakukannya dengan mahasiswa baru pada praktik "Programming-I" saya (Universitas Charles, Praha) dan sebagian besar siswa menyelesaikannya sampai akhir jam.

Jika Anda ingin melihat kode sumbernya, beri tahu saya.

Tom Holan
sumber
1
Perintah kedua adalah jalan pintas ke tampilan kelas, bukan? Atau apakah tip ini bukan tentang Visual Studio 2010?
Joel Peltonen