如何使用 VisualStudio (和/或 ReSharper)从类字段生成构造函数?

我已经习惯了许多 Java IDE (日食NetBeansIntelliJ 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 # 世界的新手,所以我想知道我是否遗漏了一些关于语言的基本知识?在 VisualStudio 中是否有一些显而易见的选项?

167188 次浏览

ReSharper 提供了一个 生成构造函数工具,您可以在其中选择需要初始化的任何字段/属性。我使用 Alt + Ins热键来访问这个。

C # 在 VisualStudio2010中添加了一个新特性,称为使用生成。其目的是从使用模式生成标准代码。其中一个特性是基于初始化模式生成构造函数。

该特性可以通过检测到模式时出现的智能标记进行访问。

例如,假设我有以下类

class MyType {


}

我在申请表中写下如下内容

var v1 = new MyType(42);

采用 int的构造函数不存在,因此会显示一个智能标记,其中一个选项是“ Generate structor stub”。选择将 MyType的代码修改为以下内容的。

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

您可以编写一个宏来完成这项工作——您可以使用 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

下面是我为此目的使用的宏。它将从具有私有 setter 的字段和属性生成构造函数。

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

也许你可以试试这个: http://cometaddin.codeplex.com/

我使用以下技巧:

我选择带有 data-member 的类声明,然后按下:

Ctrl + CShift + Ctrl + CCtrl + V.

  • 第一个命令将声明复制到剪贴板,
  • 第二个命令是调用 PROGRAM 的快捷方式
  • 最后一个命令通过剪贴板中的文本覆盖所选内容。

程序从剪贴板中获取声明, 查找类的名称,查找所有成员及其类型, 生成构造函数并将其复制回剪贴板。

我们在我的“程序设计 I”实践(查尔斯大学,布拉格)与新生一起做 而且大多数学生都能做到一个小时结束。

如果你想看源代码,告诉我。

下面修改了 JMarsh的 VisualStudio 宏,以根据类中的字段和属性生成构造函数。

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

使用 ReSharper 8或更高版本可以很容易地做到这一点。ctorfctorpctorfp代码片段生成构造函数,这些构造函数填充类的所有字段、属性或字段和属性。

对于 VisualStudio2015,我发现 延期就是这样做的。它似乎工作得很好,并有相当高的下载量。因此,如果你不能或不想使用 ReSharper,你可以安装这个代替。

你也可以获得它 通过 NuGet

在 VisualStudio2015Update3中,我有这个特性。

只需突出显示属性,然后按 Ctrl + .,然后按 生成构造函数

例如,如果您突出显示了两个属性,它将建议您创建一个具有两个参数的构造函数,如果您选择了三个,它将建议一个具有三个参数,以此类推。

它还可以与 VisualStudio2017和2019一起使用。

Auto generate shortcut visualisation

从 VisualStudio2017开始,这看起来是一个内置特性。当光标在类主体中时,点击 Ctrl + .,并从 快速操作和重构下拉列表中选择 “生成构造函数”

在 Visual Studio 中单击其中一个字段-> 单击灯泡-> 生成构造函数-> 选择字段