¿Cómo puedo generar un constructor desde los campos de la clase usando Visual Studio (y / o ReSharper)?

Me he acostumbrado a muchos de los IDE de Java (Eclipse, Netbeans, IntelliJ) que le proporcionan un comando para generar un constructor predeterminado para una clase basada en los campos de la clase.

Por ejemplo:

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

Tener un constructor que rellene todos los campos de un objeto es una tarea tan común en la mayoría de los lenguajes de OOP, supongo que hay alguna forma de ahorrar tiempo escribiendo este código repetitivo en C #. Soy nuevo en el mundo de C #, entonces me pregunto si me estoy perdiendo algo fundamental sobre el lenguaje. ¿Hay alguna opción en Visual Studio que sea obvia?

Resharper ofrece una herramienta Generar constructor donde puede seleccionar cualquier campo / propiedades que desee inicializar. Uso la tecla de acceso rápido Alt + Ins para acceder a esto.

En visual studio 2015 Update3 tengo esta característica.

solo resaltando propiedades y luego presiona ctrl + . y luego presione Generar constructor.

Por ejemplo, si ha resaltado 2 propiedades, le sugerirá que cree un constructor con 2 parámetros y, si ha seleccionado 3, le sugerirá uno con 3 parámetros, y así sucesivamente.

también funciona con VS2017.

auto generar visualización de acceso directo

C # agregó una nueva característica en Visual Studio 2010 llamada generate from use. La intención es generar el código estándar a partir de un patrón de uso. Una de las características es generar un constructor basado en un patrón de inicialización.

La función es accesible a través de la etiqueta inteligente que aparecerá cuando se detecte el patrón.

Por ejemplo. Digamos que tengo la siguiente clase

 class MyType { } 

Y escribo lo siguiente en mi aplicación

 var v1 = new MyType(42); 

Un constructor que toma un int no existe, por lo que aparecerá una etiqueta inteligente y una de las opciones será “Generar el código auxiliar del constructor”. Seleccionar eso modificará el código para MyType para ser el siguiente.

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

Podría escribir una macro para hacer esto: usaría el analizador de Visual Studio para recuperar información sobre los miembros de la clase.

Escribí una macro similar. (Compartiré el código a continuación). La macro que escribí es para copiar todos los constructores en una clase base cuando heredas de ella (útil para clases como Exception que tienen muchas sobrecargas en el ctor).

Aquí está mi macro (una vez más, no resuelve tu problema, pero probablemente puedas modificarlo para hacer lo que quieras)

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

Aquí hay una macro que uso para ese propósito. Generará un constructor a partir de campos y propiedades que tengan un setter privado.

 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 

Tal vez podrías probar esto: http://cometaddin.codeplex.com/

A partir de VS 2017, esto parece ser una función incorporada. Presiona Ctrl + . mientras el cursor está en el cuerpo de la clase, y seleccione “Generar constructor” en el menú desplegable Acciones rápidas y Refactorizaciones.

Me doy cuenta de que esta es una vieja pregunta, pero si alguien aún la está buscando, puede hacerlo fácilmente con Resharper 8+. Los ctorf , ctorp y ctorfp generan constructores que pueblan todos los campos, propiedades o campos y propiedades de una clase.

Aquí está la macro VS de JMarsh modificada para generar un constructor basado en los campos y propiedades de la clase.

 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 

Para VS 2015 encontré una extensión que hace justamente esto. Parece funcionar bien y tiene una cantidad razonablemente alta de descargas. Entonces, si no puede o no quiere usar Resharper, puede instalar este en su lugar.

ACTUALIZAR

También puedes adquirirlo a través de nuget

Estoy usando el siguiente truco:

Selecciono la statement de la clase con los miembros de los datos y presiono:

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

  • El primer comando copia la statement en el portapapeles,
  • El segundo comando es un atajo que invoca el PROGRAMA
  • El último comando sobrescribe la selección por texto del portapapeles.

El PROGRAMA obtiene la statement del portapapeles, encuentra el nombre de la clase, encuentra todos los miembros y sus tipos, genera el constructor y lo copia de nuevo en el portapapeles.

Lo estamos haciendo con estudiantes de primer año en mi práctica de “Progtwigción I” (Charles University, Praga) y la mayoría de los estudiantes lo hacen hasta el final de la hora.

Si quieres ver el código fuente, házmelo saber.