Visual Studio(またはReSharper)を使用してクラスフィールドからコンストラクターを生成するにはどうすればよいですか?


158

クラスのフィールドに基づいてクラスのデフォルトのコンストラクターを生成するコマンドを提供するJava IDEの多く(EclipseNetBeans、およびIntelliJ IDEA)に慣れてきました。

例えば:

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

オブジェクトのすべてのフィールドにコンストラクターを設定することは、ほとんどのOOP言語で非常に一般的なタスクです。このボイラープレートコードをC#で書く時間を節約する方法があると思います。私はC#の世界に慣れていないので、言語に関する基本的なことを見逃していませんか?Visual Studioに明らかなオプションはありますか?

回答:


124

ReSharperは、初期化するフィールド/プロパティを選択できる生成コンストラクターツールを提供します。これにアクセスするには、Alt+ Insホットキーを使用します。


これで、「やり遂げる」という点で私の質問に答えることができます。ただし、VS2010では直接サポートされていません。
Elijah

1
Jaredが以下で言及するように、VS2010は「使用法から生成」ツールを追加しましたが、私の知る限り、すでにクラスにあるフィールドに基づいてコンストラクターを生成する方法はありません。既存のものと一致しないシグニチャーを使用してクラスをインスタンス化しようとすると、コンストラクターが生成されます。
James Kolpack

わあ、これはかなり古い質問だと知っていますが、これを発見したばかりです!
ブレット

49
ReSharperは無料でないことをお伝えしておきます。
b1nary.atr0phy 2016年

184

Visual Studio 2015 Update3にはこの機能があります。

プロパティをハイライト表示してCtrl+ .を押し、次に[ コンストラクターを生成]を押すだけです。

たとえば、2つのプロパティを強調表示している場合は、2つのパラメーターを使用してコンストラクターを作成することを提案し、3つを選択した場合は、3つのパラメーターを使用してコンストラクターを作成するなどを提案します。

Visual Studio 2017でも動作します。

ショートカットの視覚化を自動生成


3
ねえ、これはVisual Studio 2015コミュニティで私のために働いた。これがあまり一般に知られていないかはわかりませんが、これはすばらしいことです。ありがとう。:)
0bserver 2017年

3
パーフェクトです。あなたが投稿した日にそれを読んだら、これで保存できたはずの仕事... xD
Timo

3
価値があるのは、C#6の読み取り専用プロパティを使用する場合、この機能はポップアップしないことです。(例public int Age { get; })一時的であっても、オプションを使用可能にするには、setterを指定する必要があります。VS2015コミュニティでテスト済み。これがVS2017で修正されたかどうかは不明です。
Chris Sinclair、

1
@PouyaSamie:C#6.0では、読み取り専用の自動プロパティをコンストラクターで割り当てることができます。例えば、これを参照してください。github.com/dotnet/roslyn/wiki/...
クリス・シンクレア

5
これは完璧なソリューションです!私はこれを本当の解決策とマークします!
ヴァーツラフHoluša

29

C#は、generate from usageと呼ばれるVisual Studio 2010の新機能を追加しました。その目的は、使用パターンから標準コードを生成することです。機能の1つは、初期化パターンに基づいてコンストラクターを生成することです。

この機能は、パターンが検出されたときに表示されるスマートタグを介してアクセスできます。

たとえば、次のクラスがあるとします

class MyType { 

}

そして、私は私のアプリケーションに以下を書きます

var v1 = new MyType(42);

を取得するコンストラクターがint存在しないため、スマートタグが表示され、オプションの1つは「コンストラクタースタブの生成」になります。これを選択すると、コードMyTypeが次のように変更されます。

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

15

これを行うマクロを記述できます。VisualStudioのパーサーを使用して、クラスのメンバーに関する情報を取得します。

私は同様のマクロを書いた。(以下のコードを共有します)。私が書いたマクロは、基本クラスから継承するときに、基本クラスのすべてのコンストラクターを転送するためのものです(Exceptionのような、ctorに多くのオーバーロードがあるクラスに役立ちます)。

ここに私のマクロがあります(これも問題を解決しませんが、あなたが望むように変更することができます)


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
演算子がありません:「If searchParam.Type.AsFullName funcParam.Type.AsFullName Then」は「If searchParam.Type.AsFullName = funcParam.Type.AsFullName Then」
LTR

1
@LTRグレートキャッチ-「If searchParam.Type.AsFullName <> funcParam.Type.AsFullName」であることになっていることを除いて。山括弧のエスケープを逃しました-それらはエディターには表示されましたが、ビューには表示されませんでした。ありがとう!
JMarsch 2012

13

Visual Studio 2017では、これは組み込み機能のようです。ヒットCtrl+ .カーソルがクラス本体であり、かつ選択しながら、「コンストラクタを生成」からクイックアクションとリファクタリングのドロップダウン。


11

これが、その目的で使用するマクロです。プライベートセッターを持つフィールドとプロパティからコンストラクターを生成します。

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

ヌル参照例外を回避するために2行に「その後でない場合String.IsNullOrEmpty(ParameterNameが)AND NOT visitedNames.ContainsKey(ParameterNameが)」::私は、行を分割しなければならなかった
cedd

9

多分あなたはこれを試すことができます:http//cometaddin.codeplex.com/


CodePlexはシャットダウンされました(ただし、リンクは現在、ダウンロード可能なアーカイブにより、まだある程度有効です)。ただし、リンクを更新してみてください(プロジェクトが他の場所に移動された場合)。または、現在のリンクが将来切断された場合に、災害を防止するための対策を講じます。
Peter Mortensen

5

これは、ReSharper 8以降で簡単に実行できます。ctorfctorp、とctorfpスニペットは、クラスのすべてのフィールド、プロパティ、またはフィールドやプロパティを移入コンストラクタを生成します。


4

ここでのJMarshさんのVisual Studioマクロは、クラスのフィールドやプロパティに基づいてコンストラクタを生成するように変更しました。

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

Visual Studio 2015の場合これを行う拡張機能を見つけまし。うまく機能しているようで、ダウンロード数もかなり多くなっています。したがって、ReSharperを使用できない場合、または使用したくない場合は、代わりにReSharperをインストールできます。

NuGetから取得することもできます。


-3

私は次のトリックを使用しています:

データメンバーを含むクラスの宣言を選択し、次のキーを押します。

Ctrl+ CShift+ Ctrl+ CCtrl+ V

  • 最初のコマンドは宣言をクリップボードにコピーし、
  • 2番目のコマンドは、プログラムを呼び出すショートカットです
  • 最後のコマンドは、クリップボードのテキストで選択を上書きします。

PROGRAMは、クリップボードから宣言を取得し、クラスの名前を見つけ、すべてのメンバーとそのタイプを見つけ、コンストラクターを生成して、すべてをクリップボードにコピーします。

私は「Programming-I」の練習で新入生と一緒にやっています(チャールズ大学、プラハ)。ほとんどの学生は1時間の終わりまでそれを行います。

ソースコードをご覧になりたい場合はお知らせください。


1
2番目のコマンドはクラスビューへのショートカットです。または、このヒントはVisual Studio 2010に関するものではありませんか?
Joel Peltonen 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.