Другим случаем, когда NullReferenceExceptions
может случиться, является (неправильное) использование оператора as
:
class Book {
public string Name { get; set; }
}
class Car { }
Car mycar = new Car();
Book mybook = mycar as Book; // Incompatible conversion --> mybook = null
Console.WriteLine(mybook.Name); // NullReferenceException
Здесь Book
и Car
являются несовместимыми типами; a Car
не может быть преобразован / передан в Book
. Когда этот сбой завершается неудачно, as
возвращает null
. Используя mybook
после этого, вы вызываете NullReferenceException
.
В общем случае вы должны использовать cast или as
, как показано ниже:
Если вы ожидаете преобразования типа в всегда преуспевает (т. е. вы знаете, какой объект должен быть впереди времени), тогда вы должны использовать cast:
ComicBook cb = (ComicBook)specificBook;
Если вы не уверены в типе, но хотите попробовать , чтобы использовать его как определенный тип, затем используйте as
:
ComicBook cb = specificBook as ComicBook;
if (cb != null) {
// ...
}
Resharper предлагает инструмент Generate Constructor , где вы можете выбрать любое поле / свойства, которые вы хотите инициализировать. Я использую горячую клавишу Alt + Ins для доступа к ней.
Вот макрос, который я использую для этой цели. Он будет генерировать конструктор из полей и свойств, имеющих частный сеттер.
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
Здесь изменен макрос VS JMarsh для генерации конструктора на основе полей и свойств в классе.
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
Вы можете написать макрос для этого - вы бы использовали анализатор 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
C # добавлена новая функция в Visual Studio 2010, называемая generate из использования. Цель состоит в том, чтобы сгенерировать стандартный код из шаблона использования. Одной из особенностей является создание конструктора, основанного на шаблоне инициализации.
Функция доступна через смарт-тег, который будет отображаться при обнаружении шаблона.
Например. Допустим, у меня есть следующий класс
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;
}
}
Я использую следующий трюк:
Я выбираю объявление класса с элементами данных и нажимаю:
Ctrl + C, Shift + Ctrl + C, Ctrl + V.
PROGRAM получает объявление из буфера обмена, находит имя класса, находит всех членов и их типы, генерирует конструктор и копирует его все вернуться в буфер обмена.
Мы делаем это с первокурсниками в моей практике «Программирование-I» (Карл-Университет, Прага), и большинство студентов добираются до конца часа.
Если вы хотите увидеть исходный код, дайте мне знать.
Как и VS 2017, это выглядит как встроенная функция. Нажмите Ctrl +. в то время как ваш курсор находится в классе, и выберите «Generate Constructor» из раскрывающегося списка «Быстрые действия и рефакторинг».
public int Age { get; }
). Они должны иметь в настройках, указанных, даже если временно, для того, чтобы опция была доступна. Протестировано в VS2015 Сообщество; не уверен, что это было исправлено в VS2017.
– Chris Sinclair
17 May 2017 в 15:47
Я понимаю, что это старый вопрос, но если кто-то все еще ищет его, вы можете сделать это легко с помощью Resharper 8+. Фрагменты ctorf
, ctorp
и ctorfp
генерируют конструкторы, которые заполняют все поля, свойства или поля и свойства класса.
Возможно, вы могли бы попробовать это: http://cometaddin.codeplex.com/
Для VS 2015 я нашел расширение , которое делает именно это. Кажется, что он работает хорошо и имеет достаточно большой объем загрузок. Поэтому, если вы не можете или не хотите использовать Resharper, вы можете установить этот вариант.
UPDATE
Вы также можете приобрести его через nuget
В visual studio 2015 Update3 У меня есть эта функция.
, просто выделив свойства, а затем нажмите ctrl +. а затем нажмите «Создать конструктор».
Например, если вы выделили 2 свойства, это предложит вам создать конструктор с 2 параметрами, и если вы выбрали 3, он предложит один с тремя параметрами и т. д.
также работает с VS2017.
public int Age { get; }
). Они должны иметь в настройках, указанных, даже если временно, для того, чтобы опция была доступна. Протестировано в VS2015 Сообщество; не уверен, что это было исправлено в VS2017.
– Chris Sinclair
17 May 2017 в 15:47