Làm cách nào để tôi tạo một hàm tạo từ các trường lớp bằng Visual Studio (và / hoặc ReSharper)?


158

Tôi đã quen với nhiều IDE Java ( Eclipse , NetBeansIntelliJ IDEA ) cung cấp cho bạn một lệnh để tạo một hàm tạo mặc định cho một lớp dựa trên các trường trong lớp.

Ví dụ:

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

Có một hàm tạo xây dựng tất cả các trường của một đối tượng là một nhiệm vụ phổ biến trong hầu hết các ngôn ngữ OOP, tôi giả sử rằng có một cách nào đó để tôi tiết kiệm thời gian viết mã soạn sẵn này trong C #. Tôi mới tham gia vào thế giới C #, vì vậy tôi tự hỏi liệu tôi có thiếu điều gì cơ bản về ngôn ngữ không? Có một số tùy chọn trong Visual Studio là rõ ràng?

Câu trả lời:


124

ReSharper cung cấp công cụ Tạo công cụ tạo, nơi bạn có thể chọn bất kỳ trường / thuộc tính nào bạn muốn khởi tạo. Tôi sử dụng Alt+ Insphím nóng để truy cập này.


Điều đó trả lời câu hỏi cho tôi về "hoàn thành nó." Tuy nhiên, không có hỗ trợ cho nó trong VS2010 trực tiếp, phải không?
Elijah

1
Giống như Jared đề cập bên dưới, VS2010 đã thêm một công cụ "Tạo từ sử dụng", nhưng theo như tôi có thể nói, không có cách nào để tạo một hàm tạo dựa trên các trường đã có trong lớp. Nếu bạn cố gắng khởi tạo lớp bằng một chữ ký không khớp với bất kỳ chữ ký hiện có nào, nó sẽ đề nghị tạo ra hàm tạo đó cho bạn.
James Kolpack

Ồ wow, tôi biết đây là một câu hỏi khá cũ nhưng tôi chỉ mới phát hiện ra điều này!
Brett

49
Có lẽ bạn nên đề cập đến ReSharper không miễn phí .
b1nary.atr0phy

184

Trong Visual Studio 2015 Update3 tôi có tính năng này.

Chỉ bằng cách tô sáng các thuộc tính và sau đó nhấn Ctrl+ .và sau đó nhấn Generate Con constructor .

Ví dụ: nếu bạn đã tô sáng hai thuộc tính, nó sẽ đề nghị bạn tạo một hàm tạo với hai tham số và nếu bạn đã chọn ba thuộc tính, nó sẽ gợi ý một thuộc tính có ba tham số, v.v.

Nó cũng hoạt động với Visual Studio 2017.

Tự động tạo trực quan hóa phím tắt


3
Này, điều này làm việc cho tôi trong cộng đồng Visual Studio 2015. Không chắc chắn làm thế nào điều này không được biết đến công khai, nhưng điều này là tốt đẹp. Cảm ơn. :)
Máy chủ 0bs

3
Đó là hoàn hảo. Công việc này có thể đã được lưu nếu tôi đọc nó vào ngày bạn đăng nó ... xD
Timo

3
Để biết giá trị của nó, tính năng này sẽ không bật lên nếu bạn sử dụng các thuộc tính chỉ đọc C # 6. (ví dụ, public int Age { get; }) Họ cần phải có tại setters được chỉ định, ngay cả khi tạm thời, để tùy chọn có sẵn. Đã thử nghiệm trong Cộng đồng VS2015; không chắc chắn nếu điều này đã được sửa trong VS2017.
Chris Sinclair

1
@PouyaSamie: Trong C # 6.0, các thuộc tính tự động chỉ đọc có thể được chỉ định trong hàm tạo. Xem ví dụ này: github.com/dotnet/roslyn/wiki/ Kẻ
Chris Sinclair

5
Đây là giải pháp hoàn hảo! Tôi sẽ đánh dấu đây là giải pháp thực sự!
Václav Holuša

29

C # đã thêm một tính năng mới trong Visual Studio 2010 được gọi là tạo từ việc sử dụng. Mục đích là để tạo mã tiêu chuẩn từ một mẫu sử dụng. Một trong những tính năng là tạo một hàm tạo dựa trên mẫu khởi tạo.

Tính năng này có thể truy cập thông qua thẻ thông minh sẽ xuất hiện khi mẫu được phát hiện.

Ví dụ: giả sử tôi có lớp sau

class MyType { 

}

Và tôi viết như sau trong ứng dụng của mình

var v1 = new MyType(42);

Một hàm tạo intkhông tồn tại nên thẻ thông minh sẽ hiển thị và một trong các tùy chọn sẽ là "Tạo sơ khai hàm tạo". Chọn mà sẽ sửa đổi mã cho MyTypelà sau đây.

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

15

Bạn có thể viết một macro để làm điều này - bạn sẽ sử dụng trình phân tích cú pháp của Visual Studio để lấy thông tin về các thành viên của lớp.

Tôi đã viết một macro tương tự. (Tôi sẽ chia sẻ mã dưới đây). Macro tôi đã viết là để sao chép chuyển tiếp tất cả các hàm tạo trong một lớp cơ sở khi bạn kế thừa từ nó (hữu ích cho các lớp như Exception có quá nhiều quá tải trên ctor).

Đây là macro của tôi (một lần nữa, nó không giải quyết được vấn đề của bạn, nhưng bạn có thể sửa đổi để làm những gì bạn muốn)


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
Có một toán tử bị thiếu: "If searchParam.Type.AsFullName funcParam.Type.AsFullName Then" nên là "If searchParam.Type.AsFullName = funcParam.Type.AsFullName Then"
LTR

1
@LTR Bắt tuyệt vời - ngoại trừ được cho là "Nếu searchParam.Type.AsFullName <> funcParam.Type.AsFullName". Tôi đã bỏ lỡ lối thoát trên dấu ngoặc nhọn - chúng xuất hiện trong trình chỉnh sửa, nhưng không xuất hiện trong chế độ xem. Cảm ơn!
JMarsch

13

Kể từ Visual Studio 2017, đây có vẻ là một tính năng tích hợp. Nhấn Ctrl+ .trong khi con trỏ của bạn ở trong thân lớp và chọn "Tạo trình tạo" từ trình đơn thả xuống Tác vụ nhanh và Tái cấu trúc .


11

Đây là một macro mà tôi sử dụng cho mục đích đó. Nó sẽ tạo ra một hàm tạo từ các trường và thuộc tính có bộ setter riêng.

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

Tôi đã phải chia dòng: "Nếu không phải là String.IsNullOrEmpty (tham sốName) và không truy cậpNames.ContainsKey (tham số tên) Sau đó" thành hai dòng để tránh ngoại lệ tham chiếu null:
cedd

9

Có lẽ bạn có thể thử điều này: http://cometaddin.codeplex.com/


CodePlex đã bị tắt (nhưng liên kết hiện vẫn có phần hợp lệ, với một kho lưu trữ có thể tải xuống). Nhưng có lẽ cố gắng cập nhật liên kết (nếu dự án đã được chuyển đi nơi khác). Và / hoặc thực hiện các bước để ngăn chặn thảm họa nếu liên kết hiện tại bị hỏng trong tương lai.
Peter Mortensen

5

Bạn có thể làm điều này dễ dàng với ReSharper 8 trở lên. Các ctorf, ctorpctorfpđoạn tạo cấu trúc cho cư tất cả các lĩnh vực, tài sản, hoặc các lĩnh vực và các thuộc tính của một lớp.


4

Đây là macro Visual Studio của JMarsh được sửa đổi để tạo một hàm tạo dựa trên các trường và thuộc tính trong lớp.

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

Đối với Visual Studio 2015 tôi đã tìm thấy một tiện ích mở rộng thực hiện điều này. Nó dường như hoạt động tốt và có số lượng tải xuống khá cao. Vì vậy, nếu bạn không thể hoặc không muốn sử dụng ReSharper, bạn có thể cài đặt cái này thay thế.

Bạn cũng có thể có được nó thông qua NuGet .


-3

Tôi đang sử dụng thủ thuật sau:

Tôi chọn khai báo của lớp với các thành viên dữ liệu và nhấn:

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

  • Lệnh đầu tiên sao chép khai báo vào clipboard,
  • Lệnh thứ hai là một phím tắt gọi CHƯƠNG TRÌNH
  • Lệnh cuối cùng ghi đè lựa chọn bằng văn bản từ bảng ghi tạm.

PROGRAM lấy khai báo từ bảng ghi tạm, tìm tên của lớp, tìm tất cả các thành viên và kiểu của chúng, tạo hàm tạo và sao chép tất cả lại vào bảng tạm.

Chúng tôi đang làm điều đó với sinh viên năm nhất trong thực hành "Lập trình-Tôi" của tôi (Đại học Charles, Prague) và hầu hết các sinh viên hoàn thành nó cho đến cuối giờ.

Nếu bạn muốn xem mã nguồn, hãy cho tôi biết.


1
Lệnh thứ hai là một lối tắt đến chế độ xem lớp, phải không? Hay là mẹo này không phải về Visual Studio 2010?
Joel Peltonen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.