How to limit the scope of a shared variable so that it can only be access in one function? - vb.net

Private Shared _twolettercountryCodeDict As Generic.Dictionary(Of String, String)
Private Function twolettercountrycode() As String
If _twolettercountryCodeDict Is Nothing Then
_twolettercountryCodeDict = New Generic.Dictionary(Of String, String) From {{"ty", "turkey"}, {"py", "pakinmay"}, {"ra", "romania"}, {"vm", "vietnam"}, {"bl", "brazil"}, {"et", "egypt"}, {"ka", "korea"}}
Dim listOfCountries = fileToCol(COUNTRYCODESFileName)
For Each var In listOfCountries
Dim ar = var.Split({"*"}, System.StringSplitOptions.None).ToList()
_twolettercountryCodeDict.Add(LCase(ar(1)), UCase(ar(0)))
Next
End If
Return _twolettercountryCodeDict(Me.twoletter.ToLower)
End Function
Here, I am using Private Shared _twolettercountryCodeDict As Generic.Dictionary(Of String, String)
That's because I want to share that _twolettercountryCodeDict for the whole program. I am basically implementing lazy loading. I do not want part of the code that read a text file and populate country codes are done again and again.
The thing is if I declare it as Private Shared, other methods on the same class can access that variable too. Which is not much of a problem but say I want to avoid it.
If I declare the variable as static inside the function then the twolettercountryCodeDict won't be shared.
So I am in a dilemma. What's the solution?
Let's just say that twolettercountrycode requires a private member, so it can't be a shared function. But I want _twolettercountryCodeDict to be shared and accessible only from twolettercountrycode. Can I do so?

This doesn't do precisely what you asked for, but it solves the requirement of only allowing the resource loading to be done once. You could achieve the same thing by using a Shared Constructor on a class that's solely for loading your resource.
You may also want to use a ReadOnlyDictionary (implementation) so that your dictionary can't be modified by callers.
Friend Shared ReadOnly Property twolettercountrycode As Generic.Dictionary
Get
Static _twolettercountryCodeDict As Generic.Dictionary = Nothing
If _twolettercountryCodeDict Is Nothing Then
_twolettercountryCodeDict = New Generic.Dictionary(Of String, String) From {{"ty", "turkey"}, {"py", "pakinmay"}, {"ra", "romania"}, {"vm", "vietnam"}, {"bl", "brazil"}, {"et", "egypt"}, {"ka", "korea"}}
Dim listOfCountries = fileToCol(COUNTRYCODESFileName)
For Each var In listOfCountries
Dim ar = var.Split({"*"}, System.StringSplitOptions.None).ToList()
_twolettercountryCodeDict.Add(LCase(ar(1)), UCase(ar(0)))
Next
End If
return _twolettercountryCodeDict
End Get
End Property

Related

Shared function that infers the class type and can be returned in a list

I would like to create a shared function that returns a list of instances of the classes type. Currently this is what my code looks like
class MyClass
Implements BusinessObject
Shared Function LoadAll(Of T As {BusinessObject, New})() As IEnumerable(Of T)
Dim helper = New SQLHelper()
Return helper.LoadDataTableFromDatabase("LoadTable", LoadAllProcedureName).Rows.Cast(Of DataRow).Select(Function(s) New T().FillDataRow(Of T)(s))
End Function
End Class
class MyDerivedClass Implements MyClass
End MyClass
When I go to use it, I have to use it like this:
MyDerivedClass.LoadAll(Of MyDerivedClass)()
I would like to be able to infer the type, instead of having to use the (Of MyDerivedClass) so that my code looks like MyDerivedClass.LoadAll().
Any help or keywords that I am missing to achieve this would be greatly appreciated.
Here is an extension method which (theoretically) would work on any class you define:
Public Module Module1
<Extension()> _
Public Function LoadAll(Of T As {BusinessObject, New})(ByVal x As T) As IEnumerable(Of T)
Dim LoadAllProcedureName As String = "LoadAllProcedure"
Dim helper = New SQLHelper()
Return helper.LoadDataTableFromDatabase("LoadTable", LoadAllProcedureName).Rows.Cast(Of DataRow).Select(Function(s) New T().FillDataRow(Of T)(s))
End Function
Public Sub Main()
Dim dC As New DerivedClass()
Dim allDc As IEnumerable(Of DerivedClass) = dC.LoadAll()
'::: Somewhat shorter syntax
Dim allDC As IEnumerable(Of DerivedClass) = (New DerivedClass()).LoadAll()
End Sub
End Module
But, as others have pointed out, this doesn't really clean anything up for you. More to the point, you are going to have to type (Of DerivedClass) in whatever variable you intend on populating with your enumerated DerivedClass, no?
And from what I can tell, you cannot have Shared extension methods -- should you be thinking that is the way to go.

Changing a Type to a Class causes ByRef parameters to act ByVal

I've heard advice to change from User Defined Type (UDT) to a regular Class in order to overcome the limitations of UDT, such as not being able to use For Each with a UDT.
I've also heard advice to change from a regular Class to UDT to overcome the Class limitation where you can't pass things BYREF, like...
'Function:
Public Function RemoveArticle (ByRef strMovieTitle As String)
'Expected input is like "Terminator, The"
strMovieTitle = Left(... 'removes the article.
End Function
That works fine for this call:
Dim strMovieTitle As String
strMovieTitle = "Terminator, The"
RemoveArticle strMovieTitle
But not this call:
Dim objMovie As MovieClass
objMovie.strMovieTitle = "Terminator, The"
objMovie.strMovieGenre = "Sci-Fi"
InvertArticle objMovie.strMovieTitle
Even though MovieClass defines
strMovieTitle As String
I can't go changing RemoveArticle (and every simple little function like it) to take a MovieClass parameter instead of a String parameter because there are other UDTs or Classes and String Variables that also need to use RemoveArticle.
What do I do if I need to use For Each and I also need to pass ByRef?
Is there a way a Class can work around the parameter problem?
(Using Excel 2010.)
Now I have understood your concern.
You simply can't take that approach to meet your goal. As Tim Williams has commented in your question, your best bet would be something like this:
Dim objMovie As MovieClass
Dim strMovieTitle As String
strMovieTitle = "Terminator, The"
objMovie.strMovieTitle = InvertArticle(strMovieTitle)
However, I see that this still does not satisfy your need.
My suggestion is as follows:
make your object internal, target properties Private and expose them with Property Let and Property Get. This way you can do the modifications you want to the properties either on set or on get (from within the class... rather than fixing things from outside the class).
Aside note, in regards to create a helper class (as someone has recommended to you): you could join into one class all those functions you use widely, such as RemoveArticle or InvertArticle. However, it requires to create an instance object every time you want to use them and, therefore, does not combine well with the recommendation I am giving to you (if you want just to simplify code). So having them in a Module as you do now is fine. Just to clarify: those recommendations they gave to you are unrelated to your question here.
Example A: on set
In you class MovieClass, rename first all the instances of strMovieTitle to pStrMovieTitle and add this to your code:
Private pStrMovieTitle As String
Public Property Let strMovieTitle (strIn As String)
pStrMovieTitle = InvertArticle(strIn)
End Property
Public Property Get strMovieTitle As String
strMovieTitle = pStrMovieTitle
End Property
The usage would be something like this:
Dim objMovie As MovieClass
objMovie.strMovieTitle = "Terminator, The" ' the article position gets rectified on assignation
objMovie.strMovieGenre = "Sci-Fi"
'InvertArticle objMovie.strMovieTitle ' => you don't need to do this call
Example B: on get
To keep your original string as it comes, and do apply your helpers when you get the property value. That way you always preserve the original string. However, this approach will need more rework and it's only worthy in cases where you have lots of ways to use that String in different parts of your code.
Private pStrMovieTitleSource As String
Public Property Let strMovieTitle (strIn As String)
pStrMovieTitleSource = Trim(strIn)
End Property
Public Property Get strMovieTitleSource () As String
strMovieTitleSource = pStrMovieTitleSource
End Property
Public Property Get strMovieTitleRoot () As String
strMovieTitleRoot = RemoveArticle(pStrMovieTitleSource)
End Property
Public Property Get strMovieTitle () As String
strMovieTitle = InvertArticle(pStrMovieTitleSource)
End Property
Hope it helps

So a VB interface can't have shared functions. Is there an alternative to creating dummy objects?

To avoid getting into the weeds on my particular program, let me just create a simplified case.
I have a generic class that should work on a variety of objects. Each of those objects must implement a certain interface.
What I WANT to say is something like:
Public Interface GenThing
Shared Function thing_name() As String ' This doesn't work! Can't be shared!
Sub FillOne(row As DataRow)
End Interface
public class Thing1
implements GenThing
public shared function thing_name() as string implements GenThing.thing_name
return "thing number one"
end function
public sub FillOne(row as DataRow) implements GenThing.MakeOne
... bunch of work ...
end sub
end class
public class ThingUtil(of T as {GenThing,New})
public function GetList(id as integer) as List(of T)
dim name=T.thing_name() ' This doesn't work!
dim ds as DataSet=GetData(name,id) ' bunch of work here that's the whole point of the class but not relevant to the question
dim my_list = new List(of T)
for each row as DataRow in ds.tables(0).rows
dim my_t = new T()
my_t.FillOne(row)
my_list.add(my_t)
next
return my_list
end function
end class
Do you get my problem? I need every class that implements the interface to have a function that returns a "name" that is used to get the data that is needed to create an instance of the object. But I need to know this name BEFORE I create the instance, because I need it to be able to create the instance. But VB doesn't allow an interface to have a shared function, so what I want to write doesn't work.
So what I've done is this:
I make thing_name not shared.
Then instead of simply "dim name=T.thing_name()", I write
dim dummy = new T()
dim name = dummy.thing_name()
Okay, it works, but it seems really ugly. I create an instance of the object, with all the overhead that that involves, just to get a piece of constant text.
Is there a better way? Or am I making a big deal out of nothing?
Update
I see that two people voted to close this question on the grounds that it is the same as "Why can't we have shared functions in an interface?"
I am not asking why I can't have a shared. I am saying, GIVEN that I can't, how do I solve this particular problem?
There's no really simple way of fixing this, no.
Depending on what thing_name does, however, you might approach things in a different way. If each implementation just returns a constant value, then it's effectively metadata about the class - and could be described in an attribute instead, which can be fetched at execution time. (See Type.GetCustomAttributes.) Unfortunately you can't then enforce all types implementing the interface to be decorated with the attribute - but you could write a unit test to check this pretty easily.
If thing_name needs to really do work at execution time, that's tougher. You could potentially look for a well-known shared method name instead and execute that via reflection (and again have unit tests to check that it's implemented properly).
I realize this is from a few years ago, but running into a similar problem, I wanted to offer a different solution. Pass a delegate as parameter to the ThingUtil constructor. You avoid having to put a shared method in an interface, and the constructor will force you to include the parameter at compile time.
You can add more delegates if needed, or to make it even simpler in this case, just pass name as a string instead of get_name as a delegate.
Define the delegate in the interface:
Public Interface GenThing
Delegate Function ThingNameDelegate() As String
Sub FillOne(row As DataRow)
End Interface
Public Class Thing1
Implements GenThing
Public Shared Function thing_name() As String 'name this whatever you want
Return "thing number one"
End Function
Public Sub FillOne(row As DataRow) Implements GenThing.FillOne
'do stuff
End Sub
End Class
In ThingUtil, add a member to store the delegate, a constructor parameter to to accept, and call it with .Invoke():
Public Class ThingUtil(Of T As {GenThing, New})
Private m_thing_name As GenThing.ThingNameDelegate
Public Sub New(thing_name As GenThing.ThingNameDelegate)
m_thing_name = thing_name
End Sub
Public Function GetList(id As Integer) As List(Of T)
Dim name = m_thing_name.Invoke()
Dim ds As DataSet = GetData(name, id) ' bunch of work here that's the whole point of the class but not relevant to the question
Dim my_list = New List(Of T)
For Each row As DataRow In ds.Tables(0).Rows
Dim my_t = New T()
my_t.FillOne(row)
my_list.Add(my_t)
Next
Return my_list
End Function
End Class
Finally, use it like this:
Dim tu as new ThingUtil(Of Thing1)(AddressOf Thing1.get_name)
tu.GetList(1)

How to instantiate Class object with varying number of property values

Been working a lot with custom classes lately and I love the power you can have with them but I have come across something that I'm not able to solve and/or find anything helpful online.
I have a list of a class with properties I'm looking to only store information pulled from a database into.
Public Class CustomClass
Public _Values As String
Public _Variables As String
Public ReadOnly Property Values() As String
Get
Return _Values
End Get
End Property
Public ReadOnly Property Variables() As String
Get
Return _Variables
End Get
End Property
Sub New(ByVal values As String, ByVal variables As String)
_Values = values
_Variables = variables
End Sub
End Class
I will be iterating through some database entries, and I'm looking to store them into the appropriate property when I hit them (since I won't have them all available immediately, which is part of my problem). I want to just be able to add either the value or the variable at a time and not both of them, but since I have the sub procedure 'New' passing two arguments, it will always require passing them both. I've found the only way around this is by making them optional fields which I don't feel is the right way to solve this. Is what I'm looking to do possible with a class or would it be simpler by using a structure?
You can overload the constructor:
Friend Class Foo
' using auto-implement props:
Public Property Name As String ' creates a _Name backing field
Public Property Value as Integer
Public Sub New(newN as String, newV as Integer)
' access "hidden" backing fields if you want:
_Name = newN
_Value = newV
End Sub
Public Sub New() ' simple ctor
End Sub
Public Sub New(justName As String)
' via the prop
Name = justName
End Sub
End Class
You now have 3 ways to create the object: with full initialization, partial (name only) or as a blank object. You will often need a "simple constructor" - one with no params - for other purposes: serializers, Collection editors and the like will have no idea how to use the parameterized constructors and will require a simple one.
If rules in the App were that there was no reason for a MyFoo to ever exist unless both Name and Value being defined, implementing only the New(String, Integer) ctor enforces that rule. That is, it is first about the app rules, then about coding convenience.
Dim myFoo As New Foo ' empty one
myFoo.Name = "ziggy" ' we only know part of it
Since the default of string is nothing, you could pass nothing for the value you don't have. IE
Collection.Add(New CustomClass("My Value",Nothing))
Every type has a default, so this works with more than just strings.

How to fill object variables defined in the dictionary based on JSON?

OK, that question sounds maybe a little confusing so I'll try to explain it with an example.
Pretend you have an object like this:
Class Something
Private varX As New Integer
Private varY As New String
'[..with the associated property definitions..]
Public Sub New()
End Sub
End Class
And another with:
Class JsonObject
Inherits Dictionary(Of String, String)
Public Function MakeObject() As Object 'or maybe even somethingObject
Dim somethingObject As New Something()
For Each kvp As KeyValuePair(Of String, String) In Me
'Here should happen something to use the Key as varX or varY and the Value as value for the varX or varY
somethingObject.CallByName(Me, kvp.Key, vbGet) = kpv.Value
Next
return somethingObject
End Function
End Class
I've got the 'CallByMe()' function from a previous question of myself
CallByName works different from the way you are trying to use it. Look at the documentation, it will tell you that in this particular case the correct usage would be
CallByName(Me, kvp.Key, vbSet, kpv.Value)
However, the function CallByName is part of a VB library that isn’t supported on all devices (notably it isn’t included in the .NET Mobile framework) and consequently it’s better not to use it.
Using proper reflection is slightly more complicated but guaranteed to work on all platforms.
Dim t = GetType(Something)
Dim field = t.GetField(kvp.Key, BindingFlags.NonPublic Or BindingFlags.Instance)
field.SetValue(Me, kvp.Value)