【问题标题】:How do I generate a constructor from class fields using Visual Studio (and/or ReSharper)?如何使用 Visual Studio(和/或 ReSharper)从类字段生成构造函数?
【发布时间】:2011-02-27 21:57:39
【问题描述】:

我已经习惯了许多 Java IDE(EclipseNetBeansIntelliJ 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 中是否有一些显而易见的选项?

【问题讨论】:

    标签: c# visual-studio visual-studio-2010 resharper code-generation


    【解决方案1】:

    ReSharper 提供了一个Generate Constructor 工具,您可以在其中选择要初始化的任何字段/属性。我使用 Alt + Ins 热键来访问它。

    【讨论】:

    • 这回答了我关于“完成它”的问题。但是,VS2010中并没有直接支持,对吧?
    • 就像 Jared 在下面提到的那样,VS2010 添加了一个“从使用情况生成”工具,但据我所知,没有办法根据类中已经存在的字段生成构造函数。如果您尝试使用与任何现有签名都不匹配的签名来实例化该类,它将提供为您生成该构造函数。
    • 哦,哇,我知道这是一个相当老的问题,但我才发现这个!
    • 您可能应该提到 ReSharper 不是免费的
    【解决方案2】:

    C# 在 Visual Studio 2010 中添加了一项新功能,称为从使用中生成。目的是从使用模式生成标准代码。其中一项功能是根据初始化模式生成构造函数。

    可通过检测到模式时出现的智能标签访问该功能。

    例如,假设我有以下课程

    class MyType { 
    
    }
    

    我在我的应用程序中写了以下内容

    var v1 = new MyType(42);
    

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

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

    【讨论】:

      【解决方案3】:

      您可以编写一个宏来执行此操作 - 您可以使用 Visual Studio 的解析器来检索有关该类成员的信息。

      我写了一个类似的宏。 (我将在下面分享代码)。我编写的宏用于在继承基类时将所有构造函数向前复制(对于像 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

      【讨论】:

      • 缺少一个运算符:" If searchParam.Type.AsFullName funcParam.Type.AsFullName Then" 应该是 " If searchParam.Type.AsFullName = funcParam.Type.AsFullName Then"
      • @LTR 很好——除了它应该是“如果 searchParam.Type.AsFullName funcParam.Type.AsFullName ”。我错过了尖括号上的转义——它们出现在编辑器中,但不在视图中。谢谢!
      【解决方案4】:

      这是我用于此目的的宏。它将从具有私有 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
      

      【讨论】:

      • 我不得不将这一行:“If Not String.IsNullOrEmpty(parameterName) And NotvisitedNames.ContainsKey(parameterName) Then”分成两行以避免空引用异常:
      【解决方案5】:

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

      【讨论】:

      • CodePlex 已关闭(但该链接目前仍然有效,带有可下载的存档)。但也许尝试更新链接(如果项目已移至其他地方)。和/或如果当前​​的链接将来损坏,请采取措施防止灾难发生。
      【解决方案6】:

      我正在使用以下技巧:

      我选择带有数据成员的类的声明,然后按:

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

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

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

      我们正在与我的“Programming-I”实践中的新生一起做这件事(查尔斯大学,布拉格) 并且大多数学生会在一个小时结束之前完成。

      如果您想查看源代码,请告诉我。

      【讨论】:

      • 第二个命令是类视图的快捷方式,不是吗?或者这个提示不是关于 Visual Studio 2010 的?
      【解决方案7】:

      这里是 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
      

      【讨论】:

        【解决方案8】:

        您可以使用 ReSharper 8 或更高版本轻松完成此操作。 ctorfctorpctorfp sn-ps 生成构造函数,用于填充类的所有字段、属性或字段和属性。

        【讨论】:

          【解决方案9】:

          对于 Visual Studio 2015,我发现 an extension 就是这样做的。它似乎运行良好,并且下载量相当高。因此,如果您不能或不想使用 ReSharper,您可以安装它。

          您也可以通过via NuGet获取。

          【讨论】:

            【解决方案10】:

            在 Visual Studio 2015 Update3 中,我有这个功能。

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

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

            它也适用于 Visual Studio 2017 和 2019。

            【讨论】:

            • 嘿,这在 Visual Studio 2015 社区中对我有用。不知道这不是很为人所知,但这很好。谢谢。 :)
            • 那是完美的。如果我在你发布它的那天读到它,这本可以保存的工作...... xD
            • 不管怎样,如果您使用 C# 6 只读属性,该功能不会弹出。 (例如,public int Age { get; })他们需要指定 at 设置器,即使是临时的,以使选项可用。在 VS2015 社区测试;不确定这是否已在 VS2017 中修复。
            • @PouyaSamie:在 C# 6.0 中,可以在构造函数中分配只读自动属性。请参阅此示例:github.com/dotnet/roslyn/wiki/…
            • 这是完美的解决方案!我会将此标记为真正的解决方案!
            【解决方案11】:

            从 Visual Studio 2017 开始,这似乎是一项内置功能。当您的光标位于类主体中时按 Ctrl + .,然后从 Quick Actions and Refactorings"Generate Constructor" /em> 下拉菜单。

            【讨论】:

              【解决方案12】:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2017-05-15
                • 1970-01-01
                • 2017-01-23
                • 2019-09-25
                • 2018-01-25
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多