VB.NET Call Setter from within Getter - vb.net

I have a class like this:
Public Class MyClass
Private _intList As New List(Of Integer)
Private _avg As Decimal
Public Sub Add(ByVal anInt As Integer)
_intList.Add(anInt)
End Sub
Public Property Avg() As Decimal
Get
Dim _sum As Integer = 0
For Each anInt In _intList
_sum += anInt
Next
Avg = If((_intList.Count > 0), _sum / _intList.Count, _avg)
Return _avg
End Get
Set(ByVal value As Decimal)
If _avg <> value Then
_avg = value
Console.WriteLine("Value changed")
End If
End Set
End Property
End Class
The Getter is calculating average and calls Setter to save the value. For some reason I cannot understand, the average is always 0. For example:
Dim c As New Class2()
c.Add(1)
c.Add(2)
c.Add(3)
Console.WriteLine(c.Avg.ToString()) ' This will print 0
Did I do something wrong? What is the cause of this?

Wow, I think you've discovered a very strange behavior of VB: when you are inside the definition of a function, you can return a value either with Return or by using = to "set" the function's value.
Like this:
Function GetInteger() As Integer
GetInteger = 5
End Function
In the above function, the line GetInteger = 5 is basically equivalent to Return 5*.
OK, so you probably already knew that. But here's the weird part, and I had no idea this was the case until testing it just now (admittedly, on Mono, but I am seeing the same behavior you are): apparently this applies to property getters as well. So look at this line:
Avg = If((_intList.Count > 0), _sum / _intList.Count, _avg)
You're actually not calling the property setter there; you're setting the return value for the getter. You can verify this by removing the line Return _avg; suddenly you will see your getter starts returning the actual average.
*Not exactly the same, as you could later set GetInteger to something else without returning immediately whereas using Return ensures the function returns right away.

This is by design and explicitly mentioned in the Visual Basic Language Specification, chapter 9.7.1:
A special local variable, which is implicitly declared in the Get
accessor body's declaration space with the same name as the property,
represents the return value of the property. The local variable has
special name resolution semantics when used in expressions. If the
local variable is used in a context that expects an expression that is
classified as a method group, such as an invocation expression, then
the name resolves to the function rather than to the local variable.
For example:
ReadOnly Property F(i As Integer) As Integer
Get
If i = 0 Then
F = 1 ' Sets the return value.
Else
F = F(i - 1) ' Recursive call.
End If
End Get
End Property
Solve your issue by assigning the _avg field directly. Property getters with side-effects like this is best avoided.

Your setters and getters really should just be returning properties, and not doing the calculations themselves. Try creating a method like calcAvg() that does the average calculations, and on the Add() call that method (which should internally not re-perform the whole average calculation, but simply update it. Let me know if you're not sure how to do that). That calcAvg() method will set the _avg instance variable.
Also, I'm not sure it really makes send to have a setter for the average. An average of numbers is a derived property, not something that should be set by an external user.

MrDanA answer is more correct than what I'm going to give you, however, I believe the reason why you are getting the value of 0 is because you are never setting the variable _avg to anything. After you do your AVG calculation if you do:
_avg = AVG
return _avg
I get a value of 2 when I do this.
Like I said before though... MrDanA's answer is a better way to go.

Related

Performance Implications of Dimming Locally vs Assigning to Function Name (VB.NET)

Can't show the actual code because proprietary software, but essentially something like this
Public FunctionA(ByVal paramOne As Int32) As Int32
FunctionA = SomeCalculation()
If FunctionA < paramOne Then Return FunctionA
Return 0
End Function
versus
Public FunctionA(ByVal paramOne As Int32) As Int32
Dim temp As Int32 = SomeCalculation()
If temp < paramOne Then Return temp
Return 0
End Function
This is a really simplified example, but I was wondering what VB.NET would be doing under the hood and therefore which is generally considered better coding practice
You're asking the wrong question. That first code snippet is not something you should ever be using at all. The whole point of using the implicitly-named variable is that it gets returned by default. The "proper" way to use it is like this:
Public FunctionA(ByVal paramOne As Int32) As Int32
FunctionA = SomeCalculation()
If FunctionA >= paramOne Then Return 0
End Function
As you can see, if nothing is explicitly returned, the value of that variable is implicitly returned.
The issue is here is not what happens under the hood. It's what happens right before your eyes. VB does not require that you use parentheses when calling a method so, if a method has no parameters, you can call it simply using its name. That means that, inside the method, using that implicitly-named local variable looks exactly like a call to the method anywhere else. That's inconsistent and error-prone, therefore it's bad.
Making your code readable should be your first concern and you should only sacrifice that readability for genuine gains in other areas. There is no gain to using that implicitly-named variable so don't use it. Declare all your variables and always use explicit Return statements.

Datarow's items not being treated as Integer in Extension Method, but exception implies it is being treated as an Integer

I implemented an extension method on Integer (this is a simplified example that also shows the error)
<Extension()>
Public Function IsPositive(ByVal item As Integer) As Boolean
Return item > 0
End Function
I then try to call the extension method on a datarow's item:
Dim dtMyTable As DataTable
dtMyTable = GetInfoFromDatabase()
If dtMyTable.Rows(0).Item("nCount").IsPositive() Then
This gives me the exception:
Public member 'IsPostive' on type 'Integer' not found.
I assume this is because dtMyTable.Rows(0).Item("nCount") is actually an object, not an integer. The exception seems to understand that that isn't the case, so I'm not sure why that's different, but it is.
However, if I try to call the same method as if it's just a regular method, it works without complaint
If IsPositive(dtMyTable.Rows(0).Item("nCount")) Then
I would rather call it the former way. I know it's possible to just save the value to a variable and then call the extension on that variable, but that seems like a needless extra step.
Is there any way to get the former method to work without adding an extra variable assignment every time I need to call it, or changing the extension method to work on Objects?

Assign null value to data row

I am trying to write one line IF condition when assign value to property.
I've tried these syntax in VB.NET, type_of_documents is nullable integer:
1) vehicle.type_of_documents = If(vehicle.Istype_of_documentsNull, SqlTypes.SqlInt32.Null, vehicle.type_of_documents)
2) vehicle.type_of_documents = If(vehicle.Istype_of_documentsNull, DBNull.Value, vehicle.type_of_documents)
3) vehicle.type_of_documents = If(vehicle.Istype_of_documentsNull, Nothing, vehicle.type_of_documents)
Well, I am a little pushy to do this in one line. Somehow, all these syntax have failed to assign null value to my database. Syntax 1 & 2 have thrown cast integer exception. Syntax 3 is no error but no change/update value in database (same as previous).
Can anyone show me better syntax? Since I am really not into VB.NET.
Thanks in advance
The If operator is basically generic, i.e. the type it returns is inferred from the type of the second and third arguments. That means that those two arguments have to be the same type or one must assignable from the type of the other. A simple cast is all you need:
vehicle.type_of_documents = If(vehicle.Istype_of_documentsNull,
CObj(DBNull.Value),
vehicle.type_of_documents)
Now that the first argument is specified to be type Object, the two arguments share a type.
Return type of If method will be inferred from result parameters (as #jmcilhinney already answered).
In 1 and 2 samples you get cast exception because Integer cannot be casted to SqlTypes.SqlInt32.Null and DbNull.Value types.
Third sample compile because Nothing can be introduced as Integer, it simply default value of Integer which equals 0.
If you want return DbNull.Value in case when type_of_documents has no value - you need cast results in their common type, which is Object.
Instead of If method you can create extension method which return common type(object) required by SqlParameters
Public Module Extensions
<Extension>
Public Function GetValueOrDbNull(this As Integer?) As Object
If this.HasValue Then Return this
Return DBNull.Value
End Function
End Module
And use it
Dim sqlValue = vehicle.type_of_documents.GetValueOrDbNull()

Weird equality issue with Generics and Enums?

I'm kind of going nuts here. I have a function something like the following. It's failing to return an object. I can pass in a list, I can see in QuickWatch that x.RB = theRb for at least one of the items in the list, yet it doesn't exit the loop (via the Return). The loop continues.
The list I am passing in is a subclass of aXXX.
Property RB on class aXXX is of type RBEnum.
Also, I originally used Linq for this but was getting "no matching items" exceptions.
Private Shared Function GetX(Of T As aXXX)(ByVal a As List(Of T),
ByVal theRb As RBEnum) As T
For Each x As T In a
If (x.RB = theRb) Then Return x
Next
Return Nothing
End Function
Any suggestions or ideas on why this isn't working?
I would recommend trying:
If (x.RB.Equals(theRb)) Then Return x
Can you cast the Enum into an Integer and then compare?
If CInt(x.RB)=CInt(theRb) Then Return x
I'm not sure how your original where statement was written but this should produce the result you're looking for:
Private Shared Function GetX(Of T As aXXX)(ByVal a As List(Of T),
ByVal theRb As RBEnum) As T
Return a.Where(Function(x) x.RB = theRb).FirstOrDefault()
End Function
I have resolution. I can't fully explain it though.
The list of items I'm passing in are a subclass of the class aXXX. The subclass did not properly override the RB property from the base class -- no Overloads / Overrides / Shadows. This kind of gives explanation as to why QuickWatch reports True on the match -- maybe this subclass property was hiding the "real" property value that was in the test?
Anyway, by taking out the property in the subclass all together or adding an Overloads, the For Each behaves as one would expect. I can even go back to the original Linq version I had in the function.
I guess this came down to oversight / sloppy coding on my part. But the issue was masked pretty well by the fact that QuickWatch reported "false positives"!
Thanks to everyone for the suggestions and help.

Can a constructor access the name of the variable its object is being store to?

I want the following code to set the 'name' field of the myClass instance to be equal to 'super_awesome_name':
super_awesome_name = myClass();
where the myClass constructor is as follows:
function obj = myClass()
obj.name = ???;
end
Is there some way to do this in Matlab? Right now I can accomplish something very similar with assignin, but it makes the syntax very unnatural:
myClass(super_awesome_name);
where the myClass constructor is as follows:
function obj = myClass(var_name)
obj.name = assignin('caller',var_name, obj);
end
As far as I know, there is no convenient way of doing so. You can see this yourself since MATLAB will have to perform some when constructing an object like that.
First construct the object
Store that object in super_awesome_name
But step 1 needs information from step 2 (i.e. the name of the variable). So you will have to work around that.
You solution may work, but it leaves a lot of room for error. Just consider the case when super_awesome_name is undefined before the construction: this will throw an exception due to an unitialized variable.
IMHO it is easier to make a constructor that accepts a string containing the name (or possibly a variable, as you can get the variable name of an argument by using inputname). Using inputname, however is just a bit clearer than using assignin.
Why do you need such functionality? I'm thinking of a situation where you might do something like:
x = myObjectThatHasAName(); % x.name contains 'x'
y = myObjectThatHasAName(); % y.name contains 'y'
x = y; % x.name contains 'y' !
So that might be the behavior you are looking for, but it may become very confusing if you pass the variables along a lot. It looks to me like you are mixing debugging information into your actual computations.
If you need the functionality to determine the actual variable name in another method or function, just use inputname. Then you won't need to keep track of the variable name yourself with all confusing consequences. That would be what I'd propose to do in most cases. You can see an example in the MATLAB documentation.
On the other hand, I think the clearest way to store the variable name inside the object, could be something like:
classdef myObjectThatHasAName
properties
name = '';
end
methods
function obj = myObjectThatHasAName()
end
function obj = storeName(obj,name)
if nargin < 2
name = inputname(1);
end
if ~isempty(name) % && isempty(obj.name)
obj.name = inputname(1);
end
end
end
end
I haven't tested this (lack of MATLAB on my current computer), but the following code should do it:
x = myObjectThatHasAName();
x = x.storeName();
% or x = storeName(x);
% or x = storeName(x,'x');
If you don't mind the overhead, you could always incorporate the storeName into each other method (and forget about calling storeMethod outside of the methods). This keeps track of the variable name where the object resided before the last assignment. A possible method might look like this:
function obj = otherMethod(obj,arguments)
obj = obj.storeName(inputname(1));
% actual code for the method
end
That way the name is set when you first call any method on the object (this almost does what you want, but I guess it will do what you need in most cases). You can of course adjust the code to just remember the first assignment (just comment out the code in storeName).