Switching Byref to Byval on method calls
I have many warnings raised due to:
"Implicit conversion from xxxx to yyyy in copying the value of 'ByRef' parameter zzzz back to the matching argument."
My feeling is that it would be safe to change the function parameters from byref to byval as nothing special is being done with the reference type pointers inside these methods the reference types are simply being used and I think the behaviour would be exactly the same if running with a copy a pointer rather than the original.
Another consideration is that I have two classes which inherit from a base class. The same situation is occuring in that the byref params are causing implicit casting from the base class to the narrower concrete class. Again I can't see any problems with this code running byval either.
Does anyone have any tips regarding use of parameters in functions when dealing with reference types?
Some of the other things that are currently being passed around byref in my project are database connection objects i.e. OracleConnection and SqlConnection. Is there any good reason for passing these around byref?
Example 1
Implicit conversion from 'Object' to 'Integer' in copying the value of 'ByRef' parameter 'value' back to the matching argument.
Calling code:
cmd = New SqlCommand()
cmd.Parameters.Add(CreateParameter("Alpha", SqlDbType.Int,ParameterDirection.Input, -1, AlphaValue))
Function:
Private Function CreateParameter(ByVal parameterName As String, ByVal dbType As SqlDbType, ByVal direction As ParameterDirection, ByVal size As Integer, ByRef value As Object) As SqlParameter
Dim retParam As SqlParameter
retParam = New SqlParameter(parameterName, dbType)
retParam.Direction = direction
retParam.Size = size
retParam.Value = value
Return retParam
End Function
Example 2
Implicit conversion from 'System.Data.IDataReader' to 'System.Data.SqlClient.SqlDataReader' in copying the value of 'ByRef' parameter 'reader' back to the matching argument.
Calling code:
Dim reader As new SqlDataReader
ReleaseReader(reader)
Method:
Public Sub ReleaseReader(ByRef reader As IDataReader)
If reader IsNot Nothing Then
If Not reader.IsClosed Then
reader.Close()
End If
reader.Dispose()
End If
End Sub
When defining a method in VB.Net, or C# for that matter, the you should pass parameters by value (ByVal) unless you need to take advantage of ByRef semantics. If you are not resetting the parameter value within the method then you should definitely turn these into ByVal calls.
If you are resetting the reference but not taking advantage of it from the call site then I would write a helper method which takes the parameter ByVal and calls into the one taking it ByRef. This will remove the warning because the resulting code won't be subject to narrowing conversion errors.
For example:
Public Sub ExampleMethod(ByRef p1 As Object)
p1 = "foo"
End Sub
Public Sub ExampleMethodWrapper(ByVal p1 as Object)
ExampleMethod(p1)
End Sub
Public Sub Test()
Dim v1 As String = "hello"
Dim v2 As String = "world"
ExampleMethod(v1) ' Warning generated
ExampleMethodWrapper(v2) ' No warning
End Sub
Related
I would like to add the "Move" method as an extension method to the "List(Of...)".
I would like to add this to the generic list, not to a specific list.
My approach is this:
Imports System.Runtime.CompilerServices
Module ExtensionMethods
<Extension()>
Public Sub Move(ByRef uBase As List(Of T), ByVal index As Integer, ByVal newIndex As Integer)
Dim item As T = uBase.Item(index)
uBase.RemoveAt(index)
uBase.Insert(newIndex, item)
End Sub
End Module
The compiler doesn't accept the "T" in the lines "uBase As List(Of T)" and in " Dim item As T ="
What should be used here?
Thank you very much!
First, don't use ByRef on the target parameter. I'll expand on that later, because I want to skip to what will fix your compilation error.
Second, in order to have a type argument T in List(Of T), it has to exist in the method definition, so you need (Of T) on the method.
Imports System.Runtime.CompilerServices
Module ExtensionMethods
<Extension()>
Public Sub Move(Of T)(ByVal uBase As List(Of T), ByVal index As Integer, ByVal newIndex As Integer)
' ^^^^^^
Dim item As T = uBase.Item(index)
uBase.RemoveAt(index)
uBase.Insert(newIndex, item)
End Sub
End Module
Rule: An extension method should never** accept the target instance using ByRef.
**Exception to the rule: Certain value (Structure) types may need to be passed by reference to achieve reference type-like behavior (although value types should be immutable if at all possible) or to achieve better performance (in C#, you use the in keyword so the compiler prevents mutation of the instance).
Take this extension method, for example:
Module ExtensionMethods
<Extension()>
Public Sub ConfuseMe(Of T)(ByRef list as List(Of T))
list = New List(Of T)
End Sub
End Module
Dim myList As List(Of Integer)
Dim myList2 = myList ' A copy of the reference, but only one list object involved.
myList.Add(0)
myList.Add(1)
myList.Add(2)
myList.ConfuseMe() ' Call an extension method that can MODIFY myList
myList no longer points to the same instance. myList2 points to the original instance while myList points to the new one created in ConfuseMe. There's no reason the caller should expect that to happen.
So why would you ever do something like this? You probably wouldn't. But based on some of the comments and the confusion between references vs. references to references, I could see it accidentally happening. Using ByVal prevents it from ever becoming a difficult-to-track-down bug.
While it's possible in an extension method, you can't do that with a regular instance method.
Class TestClass
Sub ConfuseMe()
Me = New TestClass() ' Not possible on a Class
End Sub
EndClass
Dim x As New TestClass()
x.ConfuseMe() ' You wouldn't expect 'x' to refer to a different instance upon return
You can't do that. It won't allow you to assign to Me (again, value types are the exception), and you wouldn't expect x to point to a new instance after a call like this.
By the same token, it doesn't make sense to do it in an extension method, where the purpose is to behave like an instance method. And since you don't need to change the caller's variable, there's no need to take a reference to it. Just deal with the direct reference to the object instance by accepting it with ByVal.
When declaring a procedure in VB.Net, where a passed parameter is an object, is it possible to specify several possible object types in an "Or" type of syntax?
For instance, I want to pass one of the "list" controls so the procedure can access the .Items collection. But if I attempt to generalize and specify the parameter as Windows.Forms.Control, an error is generated because .Items is not a member of the .Control object.
I see mention of the "Type List" in the VB language reference, and this seems to almost be what I want, but not quite.
Here are some bits of code to demonstrate the issue...
Friend Sub ListFill( _
ByRef SourceControl As Windows.Forms.WebBrowser, _
ByRef TargetControl As Windows.Forms.Control)
TargetControl.Items.Add(SourceControl.DocumentTitle)
' Error: 'Items' is not a member of 'System.Windows.Forms.Control'.
In a general sense, I need syntax like this...
Friend Sub name ( ByRef varname As { type1 Or type2 Or ... } = defaultvalue )
But in terms of actual working code, this is as far as I got...
Friend Sub ListFill( _
ByRef SourceControl As Windows.Forms.WebBrowser, _
Optional ByRef TargetControl As Windows.Forms.ListBox = Nothing, _
Optional ByRef TargetControl As Windows.Forms.ComboBox = Nothing)
'Error: Parameter already declared with name 'TargetControl'.
Can this be done?
You could check what type of control TargetControl is and then cast to that control, accessing it's properties.
Friend Sub ListFill( _
ByRef SourceControl As Windows.Forms.WebBrowser, _
ByRef TargetControl As Windows.Forms.Control)
If TargetControl.GetType() Is GetType(ListBox) Then
DirectCast(TargetControl, ListBox).Items.Add(SourceControl.DocumentTitle)
ElseIf TargetControl.GetType() Is GetType(ComboBox) Then
DirectCast(TargetControl, ComboBox).Items.Add(SourceControl.DocumentTitle)
End If
End Sub
Another solution is to overload the method.
Friend Sub ListFill( _
ByRef SourceControl As Windows.Forms.WebBrowser, _
ByRef TargetControl As Windows.Forms.ListBox)
TargetControl.Items.Add(SourceControl.DocumentTitle)
End Sub
Friend Sub ListFill( _
ByRef SourceControl As Windows.Forms.WebBrowser, _
ByRef TargetControl As Windows.Forms.ComboBox)
TargetControl.Items.Add(SourceControl.DocumentTitle)
End Sub
Read more: Procedure Overloading - MSDN
If Items collection is the only thing that you need
If Items collection is the only thing that you need from those controls and you need to read data from it, you can pass a list to your method and use it:
Public Sub DoSome(list As List(Of Object))
'Use list here, for example:
For Each item In list
MessageBox.Show(item.ToString())
Next
End Sub
And to pass Items as List(Of Object):
Dim list = Me.ListBox1.Items.Cast(Of Object)().ToList()
DoSome(list)
You can also use List(Of String) or any other List(Of T) that your items are.
If you need to pass whole ListBox/ComboBox Or you need manipulation
If you need to pass the whole ListBox/ComboBox object to a method, or you need to pass items collection for manipulation, then use multiple overloads. For example for whole ListBox/ComboBox:
Public Sub DoSome(list As ListBox)
'Use list here, it's of type ListBox, for example
MessageBox.Show(list.Name)
End Sub
Public Sub DoSome(combo As ComboBox)
'Use combo here, it's of type ComboBox, for example
MessageBox.Show(combo.Name)
End Sub
And here is the usage:
DoSome(Me.ComboBox1)
DoSome(Me.ListBox1)
The same could be done for ListBox.ObjectCollection or ComboBox.ObjectCollection if you need.
#visualvincent, thank you. Your answer was closest to what I needed. I'm very much a devotee of compact & versatile code and doing the most with the least. Sometimes I spend a lot of time writing a tiny bit of code.
I was not able to make DirectCast() work. Instead, after much reading, I discover another used asked a question closely related to mine. It describe the use of the CTypeDynamic() function, and that was the last piece of my puzzle. Shown here...
'objType' is not defined... Actually, it is, so why is this happening?
Below is what I cobbled together, I've tested it and it works perfectly...
Friend Sub ListboxFill(ByRef SourceControl As Windows.Forms.WebBrowser, _
ByRef TargetControl As Windows.Forms.Control)
Dim typ As System.Type = TargetControl.GetType
Select Case typ
Case GetType(ListBox), GetType(ComboBox)
CTypeDynamic(TargetControl, typ).Items.Add(SourceControl.DocumentTitle)
End Select
End Sub
Thank you all for your help..!!
Hello. I have a small problem and I can't figure it out: how can I create an optional parameter for a form's name? For example I want to do something like this:
Private Sub Draw(ByVal Start_Pos As Point, ByVal End_Pos As Point, Optional ByVal Form_Name As Form = Cube)
End Sub
I'm not sure if what I want is possible.I just know that the code is not correct because I must specify to the program that "Cube" is a form not just a string...
A constant value is required for Optional params.
When a constant value is not possible to establish at design time for whatever reason, then the easiest trick is to set the vaue as Nothing and check if the value is nothing inside the block, if it is, then set their default value at execution time.
An example:
Private Sub Draw(ByVal startPos As Point, ByVal endPos As Point,
Optional ByVal form As Form = Nothing)
If (form Is Nothing) Then
form = Cube
End If
' ...
End Sub
An adaptation to the real problem that you'd described:
Private Sub Draw(ByVal startPos As Point, ByVal endPos As Point,
Optional ByVal formName As String = "")
If ( String.IsNullOrEmpty(formName) ) Then
formName = Cube.Name
End If
' ...
End Sub
As mentioned in documents for Optional Parameters for each optional parameter, you must specify a constant expression as the default value of that parameter.
So you can't use a form instance as default value for optional parameter.
If you need to set a form name as default value for your string optional parameter:
You can set one of your application forms full name (including namespace) as default value and then create an instance of that form using Activator.CreateInstance and Unwrap the object and use DirectCast to cast it to form and show it later.
To create other forms that their names passed as that parameter, you can use Activator.CreateInstance also you can have a Dictionary(Of String, Type) containing names and form types or Dictionary(Of String, Form) containing names and form instances and use this dictionary to get the instance of form.
I have a little problem with an interface. A bunch of my classes implement the ILayoutObject interface. A method declares a variable as ILayoutObject (defaulting it as Nothing) and then runs some code which decides which object it should be. The problem is, the evaluation code runs in a method which receives the variable as a parameter and assigns an object to it. With objects, this would be no problem. The object would be affected by the changes in the method and everything would be OK. Howeverm, when using an interface, the variable in the calling code remains Nothing and behaves like a normal variable. Does anyone have any ideas on how to circumvent that? Alas, due to code structure I am unable to use ByRef or functions :(
Here is some code:
Protected LayoutHandler As Dictionary(Of String, Action(Of Constants.OptionsEntryStructure, ILayoutElements)) = New Dictionary(Of String, Action(Of Constants.OptionsEntryStructure, ILayoutElements)) From
{
{Constants.KeyLayoutType, AddressOf KeyLayoutType}
}
Sub MakeLayOuts
Dim LayoutElement As ILayoutElements = Nothing
Dim Value = "SomeValues"
Dim key = "Key"
LayoutHandler(key)(Value, LayoutElement)
' LayoutElement remains nothing.....
End Sub
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, Layout As ILayoutElements)
Layout = New LayoutObject 'which would implement the interface
End Sub
You need to declare the parameter as ByRef if you want to alter the object to which the variable in the calling code points to:
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, ByRef Layout As ILayoutElements)
Layout = New LayoutObject 'which would implement the interface
End Sub
This is true with any reference type (classes). The fact that they are referenced with an interface makes no difference.
If you can't use ByRef, and you can't use a function to return the new object, then your only other real option would be to request a type of object which has the layout object as a property. For instance:
Public Interface ILayoutElementContainer
Public Property LayoutElement As ILayoutElements
End Interface
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, Container As ILayoutElementContainer)
Container.LayoutElement = New LayoutObject 'which would implement the interface
End Sub
I've some classes defined in a dll file. These are in the form of com api.
I'm trying to create an object of one of the class dynamically and than setting some property of that object.
When I set the property manually, it works, but when I try to invoke the same using reflection it gives the error that
Object does not match target type.
Following is my code
Private Sub SetObjectValue(ByVal SelectedObject As SAPbobsCOM.BoObjectTypes, ByVal ClassName As String, ByVal FieldName As String, ByVal SetValue As String, ByVal KeyValue As String)
Dim oObject As Object
Dim myAssembly As Reflection.Assembly = Reflection.Assembly.LoadFrom("interop.sapbobscom.dll")
Dim myType As Type = myAssembly.GetType(ClassName)
Dim myMember() As MemberInfo = myType.GetMember(FieldName)
Dim myProperty As PropertyInfo = CType(myMember(0), PropertyInfo)
Dim myMethod As MethodInfo = myProperty.GetSetMethod
oObject = oCompany.GetBusinessObject(SelectedObject)
oObject.GetByKey(KeyValue)
myProperty.SetValue(oObject, CDbl(SetValue), Nothing)
End Sub
It gives the error when SetValue method is called. Instead, if I use this like following it works fine:
oObject.CreditLimit = 129
oObject.Update
Where CreditLimit is a property of the given class, and update is a method which I have to call, after the value is set, so that the value in underlying database is updated.
Similarly GetByKey is used to retrieve the value of the object from the underlying database, where the value of the primary key field has to be passed.
Since there are multiple classes and each class has lots of different properties, therefore calling them dynamically will help a lot.
Thanks
Rahul Jain
Just tried doing what casper has suggested here. It gives an error saying - Member not found. (Exception from HRESULT: 0x80020003 (DISP_E_MEMBERNOTFOUND))
Rahul
Its done. Instead of vbSet, I used vbLet and it completed successfully.
Thanks
Rahul
I am kind of curious why you are doing this, as VB will do all of it for you. You simply have to declare as type object and then make the call, or are you using an option (I believe it is strict?) which prevents you from letting the compiler emit the reflection code for late-bound calls?
If you need to take a parameter, you should be able to use CallByName as well:
Private Sub SetObjectValue(ByVal SelectedObject As SAPbobsCOM.BoObjectTypes, ByVal ClassName As String, ByVal FieldName As String, ByVal SetValue As String, ByVal KeyValue As String)
Dim oObject As Object
oObject = oCompany.GetBusinessObject(SelectedObject)
oObject.GetByKey(KeyValue)
CallByName(oObject, FieldName, vbSet, CDbl(SetValue))
End Sub