list of class with members from derived class - vb.net

I'd like to create list of class of different devices with its designed members. It this possible?
Public lstDevices As New List(Of Device)
Public Class Device
Public strName As String
Public iKind As Integer
End Class
Then I'll have particular kind of Device named Printer
Public Class Printer : Inherits Device
public shared iAge as integer
End Class
How can I reach for ex.
lstDevices.Find(Function(p) p.strName = strName).iAge = 10
if I make clsPrinter = new Printer()
I can reach clsPrinter.iAge
But with list, is this possible?

The lstDevices-list stores Devices, so the parent class of Printer which has the iAge property. You have to cast it appropriately. But you also have to store the result of List.Find in a variable:
Dim foundDevice As Device = lstDevices.Find(Function(p) p.strName = strName)
If foundDevice IsNot Nothing AndAlso TypeOf foundDevice Is Printer Then
Dim foundPrinter As Printer = DirectCast(foundDevice, Printer)
foundPrinter.iAge = 10
End If
Another nice LINQ approach is:
Dim foundPrinters = From p In lstDevices.OfType(Of Printer)()
Where p.strName = strName
Dim firstPrinter As Printer = foundPrinters.FirstOrdefault()

I just want to remind you that iAge property of Printer class is a shared property.
I prefer the LINQ methods over LINQ queries. So I tried converting Tim's query to LINQ method (minus the where criteria). I intentionally use First() instead of FirstOrDefault(). Code looks like this:
Module Module1
Public lstDevices As New List(Of Device)
Sub Main()
lstDevices.Add(New Printer() With {.strName = "Epson", .iKind = 1})
lstDevices.Add(New Printer() With {.strName = "HP", .iKind = 2})
lstDevices.OfType(Of Printer).First().iAge = 10
Console.WriteLine(lstDevices.OfType(Of Printer).First().iAge)
Console.ReadKey(True)
End Sub
End Module
Public Class Device
Public strName As String
Public iKind As Integer
End Class
Public Class Printer : Inherits Device
Public Shared iAge As Integer
End Class
VS shows warnings in these lines:
lstDevices.OfType(Of Printer).First().iAge = 10
Console.WriteLine(lstDevices.OfType(Of Printer).First().iAge)
The warning for both lines is:
Access of shared member, constant member, enum member or nested type
through an instance; qualifying expression will not be evaluated.
It means that the expression won't be evaluated because iAge is a shared property. Even if I remove the 2 lines where I add Printer instances to the list, the code won't throw any exception. That's because iAge property is tied to the Printer class, not instance of Printer class.
Sub Main()
'lstDevices.Add(New Printer() With {.strName = "Epson", .iKind = 1})
'lstDevices.Add(New Printer() With {.strName = "HP", .iKind = 2})
' these 2 lines won't throw any exceptions eventhough the list is empty
lstDevices.OfType(Of Printer).First().iAge = 10
Console.WriteLine(lstDevices.OfType(Of Printer).First().iAge)
Console.ReadKey(True)
End Sub
So there 2 lines can be simplified to:
'lstDevices.OfType(Of Printer).First().iAge = 10
'Console.WriteLine(lstDevices.OfType(Of Printer).First().iAge)
Printer.iAge = 10
Console.WriteLine(Printer.iAge)
If you remove the Shared keyword from iAge, then the code might throw exception if the list if empty. Tim's code is safe because he use FirstOrDefault(), you can check whether firstPrinter is nothing (null) or not, then use the iAge property.

Related

Pass user input to new instance of class

Alright, I have looked through 20 pages on here and can't find what I'm looking for... I've seen it in C# and other languages.. but not Visual Basic..
Say I have a class:
Public Class Cars
Private doors as integer
Private Liters as double
Private otherStuff as string
' more code'
end class
Say I also have a Form.. an inputForm we'll call it that has numerous textboxes for users to input these characteristics. The first textbox is labeled nameTextBox. Is there any way to assign the string value of that textbox as a new car?
something to the likes of..
dim nameTextBox.value as new car
??
The fields in your class are private, so they arent of much use - no other code will be able to see those values.
Public Class Car
Public Property Make As String
Public Property Model As String
Public Property Year As Integer
' something the class may use/need but doesnt expose
Private DealerCost As Decimal
' the constructor - called when you create a NEW car
Public Sub New(mk As String, md As String)
Make = mk
Model = md
End Sub
...
End Class
By specifying only a constructor which takes params, I am saying that you cannot create a new car without specifying those properties. If the constructor takes no params, then you can create an "empty" car object and set each property individually. You can do both - called overloading - so you can create a car with or without the Make and Model at the outset.
As Public Properties, other code can examine them to see what kind of car this is.
Dim myCar = New Car("Toyata", "Corolla")
myCar.Year = 2013
myCar.Color = Color.Blue
The text used of course can come from user input:
Dim myCar = New Car(tbMake.Text, tbModel.Text)
Dim yr As Int32
If Integer.TryParse(tbYear.Text, yr) Then
myCar.Year = yr
Else
' ToDo: scold user
End If

Most efficient way to count properties in object collection which are not null

What is the fastest/most efficient way to count how many Computer objects in the collection have a MAC address (I.e. Computer.MACAddress <> "")
<Serializable>
Public Class Computer
Public Property Name As String
Public Property FQDN As String
Public Property Path As String
Public Property GUID As String
Public Property MACAddress As String
Public Property IPAddress As Net.IPAddress
End Class
<Serializable>
Public Class ComputerCollection
Inherits Collection(Of Computer)
End Class
At the moment I'm using LINQ:
Dim macCount = From comp In computers Where comp.MACAddress <> "" Select comp
ssMACDisc.Text = macCount.Count.ToString + " MAC Addresses Discovered"
Thanks in advance
LINQ also uses loops. But you should prefer the most readable way normally.
Dim nonEmptyMacs = From comp In computers
Where Not String.IsNullOrEmpty(comp.MACAddress)
Dim countNonEmptyMacs = nonEmptyMacs.Count()
If you need something more efficient you could use a Lookup(Of Tkey, TElement) to count all the null/empty macs. You can initialize a lookup only via ToLookup. If the collection doesn't change often you can persist it in a field that you can access later. Perhaps you also recreate it from the method that adds new computers. You need a custom IEqualityComparer(Of Computer) which treats null and empty macs equal:
Public Class ComputerMacComparer
Implements IEqualityComparer(Of Computer)
Public Function Equals1(x As Computer, y As Computer) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of Computer).Equals
If x Is Nothing OrElse y Is Nothing Then
Return False
End If
If String.IsNullOrEmpty(x.MACAddress) AndAlso String.IsNullOrEmpty(y.MACAddress) Then Return True
Return x.MACAddress = y.MACAddress
End Function
Public Function GetHashCode1(obj As Computer) As Integer Implements System.Collections.Generic.IEqualityComparer(Of Computer).GetHashCode
If String.IsNullOrEmpty(obj.MACAddress) Then Return 0
Return obj.MACAddress.GetHashCode()
End Function
End Class
The good thing: you can use this class for many other LINQ methods like GroupBy or Intesect.
I've used this sample data:
Dim computers As New ComputerCollection
computers.Add(New Computer With {.MACAddress = ""})
computers.Add(New Computer With {.MACAddress = nothing})
computers.Add(New Computer With {.MACAddress = "1"})
computers.Add(New Computer With {.MACAddress = "2"})
computers.Add(New Computer With {.MACAddress = "3"})
computers.Add(New Computer With {.MACAddress = Nothing})
As you can see, there are three computers that have either a null- or empty-mac.
You need one examplary "Null"-Computer that you can use later:
Dim nullComp = New Computer With {.MACAddress = Nothing}
Here is the only code that is necessary to create the lookup and to count all computers:
Dim macLookup = computers.ToLookup(Function(comp) comp, New ComputerMacComparer())
Dim countNull = macLookup(nullComp).Count() ' 3
This should be more efficient since a lookup is similar to a Dictionary. It returns an empty sequence if the key is not contained.

.net - Using Class as one parameter

I have a class with several properties.
Public Class test
Public Property a As String
Public Property b As String
Public Property c As String
Public Property d As String
Public Property e As String
Public Property f As String
Public Property g As String
End Class
In my VB.net code, I am assigning a value to each property.
I want to send the whole test class as one parameter, and use all the values inside it.
So that if I add extra parameters later on, I want them to be used dynamically, instead of writing this everytime:
Textbox1.text= test.a & test.b & test.c .......
Any way to do this?
Im not really writing the values in a textbox, but this is just an simplified example.
I think what you want is a property. You'll need to add a property to your class like:
Public Property Combination() As String
Get
Return a & b & c & d & e ...
End Get
End Property
Then to get the value you'd use
Textbox1.text = test.combination
(for more details you can see http://www.dotnetperls.com/property-vbnet)
I recommend you override the built-in ToString function. Also, to further simplify this, add a CType operator.
Public Class test
Public Property a As String
Public Property b As String
Public Property c As String
Public Property d As String
Public Property e As String
Public Property f As String
Public Property g As String
Public Shared Widening Operator CType(obj As test) As String
Return If((obj Is Nothing), Nothing, obj.ToString())
End Operator
Public Overrides Function ToString() As String
Return String.Concat(Me.a, Me.b, Me.c, Me.d, Me.e, Me.f, Me.g)
End Function
End Class
The you could just do:
Textbox1.text = test
There is a way to dynamically get and set the value of properties on any object. Such functionality in .NET is collectively referred to as Reflection. For instance, to loop through all of the properties in an object, you could do something like this:
Public Function GetPropertyValues(o As Object) As String
Dim builder As New StringBuilder()
For Each i As PropertyInfo In o.GetType().GetProperties
Dim value As Object = Nothing
If i.CanRead Then
value = i.GetValue(o)
End If
If value IsNot Nothing Then
builder.Append(value.ToString())
End If
Next
Return builder.ToString()
End Function
In the above example, it calls i.GetValue to get the value of the property, but you can also call i.SetValue to set the value of the property. However, reflection is inefficient and, if used inappropriately, it can lead to brittle code. As such, as a general rule, you should avoid using reflection as long as there is any other better way to do the same thing. In other words, you should typically save reflection as a last resort.
Without more details, it's difficult to say for sure what other options would work well in your particular situation, but I strongly suspect that a better solution would be to use a List or Dictionary, for instance:
Dim myList As New List(Of String)()
myList.Add("first")
myList.Add("second")
myList.Add("third")
' ...
For Each i As String In myList
Textbox1.Text &= i
Next
Or:
Dim myDictionary As New Dictionary(Of String, String)()
myDictionary("a") = "first"
myDictionary("b") = "first"
myDictionary("c") = "first"
' ...
For Each i As KeyValuePair(Of String, String) In myDictionary
Textbox1.Text &= i.Value
Next

How can I allow all subroutines within my program to access a certain variable?

In the code below, I want to be able to access the enteredusername and enteredpassword variables from any sub routine. How would I accomplish this?
Using rdr As New FileIO.TextFieldParser("f:\Computing\Spelling Bee\stdnt&staffdtls.csv")
rdr.TextFieldType = FieldType.Delimited
rdr.Delimiters = New String() {","c}
item = rdr.ReadFields()
End Using
Console.Write("Username: ")
enteredusername = Console.ReadLine
Console.Write("Password: ")
Dim info As ConsoleKeyInfo
Do
info = Console.ReadKey(True)
If info.Key = ConsoleKey.Enter Then
Exit Do
End If
If info.Key = ConsoleKey.Backspace AndAlso enteredpassword.Length > 0 Then
enteredpassword = enteredpassword.Substring(0, enteredpassword.Length - 1)
Console.Write(vbBack & " ")
Console.CursorLeft -= 1
Else
enteredpassword &= info.KeyChar
Console.Write("*"c)
End If
Loop
Dim foundItem() As String = Nothing
For Each line As String In File.ReadAllLines("f:\Computing\Spelling Bee\stdnt&staffdtls.csv")
Dim item() As String = line.Split(","c)
If (enteredusername = item(0)) And (enteredpassword = item(1)) Then
foundItem = item
Exit For
End If
Next
To allow ALL classes within your program access the variable, you need to make it class-level and define it with Public and Shared.
Demonstration:
Public Class MainClass
Public Shared enteredusername As String
Public Shared enteredpassword As String
Private Sub SomeSub()
' Some Code ...
' You can access it here:
enteredusername = "something"
enteredpassword = "something else"
' ... More Code ...
End Sub
End Class
Public Class AnotherClass
'Also, please note, that this class can also be in another file.
Private Sub AnotherSub()
' Some Code ...
' You can also access the variable here, but you need to specify what class it is from, like so:
Console.WriteLine(MainClass.enteredusername)
Console.WriteLine(MainClass.enteredpassword)
' ... More Code ...
End Sub
End Class
Also, on a separate note, the Public and Shared modifiers can also be used on methods. If you make a method Private or don't specify anything, the method will only be accessible from methods in the same class. If you use only Public, other classes can access the method, but they will need to create a instance of the class, like so:
Dim AC As New AnotherClass
AC.AnotherSub()
If you use both the Public and the Shared modifiers, other classes will be able to access the method directly, without creating a new instance. But, you must note, that Shared methods cannot access non-Shared methods or variables. Other classes can access Public Shared methods like so:
AnotherClass.AnotherSub()
It depends on the scope. If you want all of the subroutines in the current class to be able to access them then make them a field of the class
Class TheClassName
Dim enteredusername As String
Dim enteredpassword As String
...
End Class
If you want all subroutines in all classes and modules to be able to access them then make them a module level field
Module TheModuleName
Dim enteredusername As String
Dim enteredpassword As String
...
End Module
I recommend against this approach though. Sure it's easier in the short term because it requires less ceremony and thought on the uses of the values. But long term it serves to reduce the maintainability of your code base

Get the name of the object passed in a byref parameter vb.net

How can I get the name of the object that was passed byref into a method?
Example:
Dim myobject as object
sub mymethod(byref o as object)
debug.print(o.[RealName!!!!])
end sub
sub main()
mymethod(myobject)
'outputs "myobject" NOT "o"
end sub
I'm using this for logging. I use one method multiple times and it would be nice to log the name of the variable that I passed to it. Since I'm passing it byref, I should be able to get this name, right?
For minitech who provided the answer:
This would give you the parameter name in the method and it's type, but not the name of the variable that was passed byref.
using system.reflection
Dim mb As MethodBase = MethodInfo.GetCurrentMethod()
For Each pi As ParameterInfo In mb.GetParameters()
Debug.Print("Parameter: Type={0}, Name={1}", pi.ParameterType, pi.Name)
Next
If you put that in "mymethod" above you'd get "o" and "Object".
That's impossible. Names of variables are not stored in IL, only names of class members or namespace classes. Passing it by reference makes absolutely zero difference. You wouldn't even be able to get it to print out "o".
Besides, why would you ever want to do that?
Alternatively you could get the 'Type' of the object using reflection.
Example: (Use LinqPad to execute)
Sub Main
Dim myDate As DateTime = DateTime.Now
MyMethod(myDate)
Dim something As New Something
MyMethod(something)
End Sub
Public Class Something
Public Sub New
Me.MyProperty = "Hello"
End Sub
Public Property MyProperty As String
End Class
Sub MyMethod(Byref o As Object)
o.GetType().Name.Dump()
End Sub
Sorry to say, but this is your solution. I left (ByVal o As Object) in the method signature in case you're doing more with it.
Sub MyMethod(ByVal o As Object, ByVal name As String)
Debug.Print(name)
End Sub
Sub Main()
MyMethod(MyObject, "MyObject")
End Sub
Alternatively you could create an interface, but this would only allow you to use MyMethod with classes you design. You can probably do more to improve it, but as this code stands you can only set the RealName at creation.
Interface INamedObject
Public ReadOnly Property RealName As String
End Interface
Class MyClass
Implements INamedObject
Public Sub New(ByVal RealName As String)
_RealName = RealName
End Sub
Private ReadOnly Property RealName As String Implements INamedObject.RealName
Get
Return _RealName
End Get
End Property
Private _RealName As String
End Class
Module Main
Sub MyMethod(ByVal o As INamedObject)
Debug.Print(o.RealName)
End Sub
Sub Main()
Dim MyObject As New MyClass("MyObject")
MyMethod(MyObject)
End Sub
End Module
If your program is still in the same place relative to the code that made it, this may work:
' First get the Stack Trace, depth is how far up the calling tree you want to go
Dim stackTrace As String = Environment.StackTrace
Dim depth As Integer = 4
' Next parse out the location of the code
Dim delim As Char() = {vbCr, vbLf}
Dim traceLine As String() = stackTrace.Split(delim, StringSplitOptions.RemoveEmptyEntries)
Dim filePath As String = Regex.Replace(traceLine(depth), "^[^)]+\) in ", "")
filePath = Regex.Replace(filePath, ":line [0-9]+$", "")
Dim lineNumber As String = Regex.Replace(traceLine(depth), "^.*:line ", "")
' Now read the file
Dim program As String = __.GetStringFromFile(filePath, "")
' Next parse out the line from the class file
Dim codeLine As String() = program.Split(delim)
Dim originLine As String = codeLine(lineNumber * 2 - 2)
' Now get the name of the method doing the calling, it will be one level shallower
Dim methodLine As String = Regex.Replace(traceLine(depth - 1), "^ at ", "")
Dim methodName = Regex.Replace(methodLine, "\(.*\).*$", "")
methodName = Regex.Replace(methodName, "^.*\.", "")
' And parse out the variables from the method
Dim variables As String = Regex.Replace(originLine, "^.*" & methodName & "\(", "")
variables = Regex.Replace(variables, "\).*$", "")
You control the depth that this digs into the stack trace with the depth parameter. 4 works for my needs. You might need to use a 1 2 or 3.
This is the apparently how Visual Basic controls handle the problem.
They have a base control class that in addition to any other common properties these controls may have has a name property.
For Example:
Public MustInherit Class NamedBase
Public name As String
End Class
Public Class MyNamedType
Inherits NamedBase
public Value1 as string
public Value2 as Integer
End Class
dim x as New MyNamedType
x.name = "x"
x.Value1 = "Hello, This variable is name 'x'."
x.Value2 = 75
MySubroutine(x)
public sub MySubroutine(y as MyNamedType)
debug.print("My variable's name is: " & y.name)
end sub
The output in the intermediate window should be:
My variable's name is: x