I would like to populate a listbox with a list of installed printers in VB.net.
This works:
Dim printerList As System.Drawing.Printing.PrinterSettings.StringCollection
printerList = System.Drawing.Printing.PrinterSettings.InstalledPrinters
For Each printerName In printerList
ListBox1.Items.Add(printerName)
Next
This does not work:
ListBox1.Items.AddRange(printerList)
...because of the following type-conversion error:
Public Sub AddRange (value As
System.Windows.Forms.ListBox.ObjectCollection)': Value of type
'System.Drawing.Printing.PrinterSettings.StringCollection' cannot be
converted to 'System.Windows.Forms.ListBox.ObjectCollection'.
Is it possible to directly cast one to the other for use in AddRange() as shown? Or is the loop the only (or most efficient) way?
Well, you're dealing with 2 collections that were created before the more modern generic lists and enumerables, so their use is less fluid.
In this case, the AddRange method accepts either another ObjectCollection instance (not your case), or an array of Objects. If you want to benefit from the latter, you'll need to transform the StringCollection instance to an array of Objects. Here is how this can be done:
ListBox1.Items.AddRange(printerList.Cast(Of Object)().ToArray())
That said, I would stick with your current For Each loop. It is very readable, and doesn't create an intermediate array. But, I doubt either choice will make much difference, so pick your favorite.
Related
I am working on retrofitting an application written in VB.NET. It is a simple program that uses 0.5GB (!) of RAM, because of a large amount (30+) of globally defined arrays, like:
Public Temp(1000000) As Double
Public ThisIsAnotherVariable(5000, 10) As String
Public ThisIsAVeryLargeArray(64, 50000) As Double
Most of the time, these large "buffers" are barely used, so I would like to convert them to use something from Collections.Generic. Is there any semi-transparent way to convert these? Or a bit of trickery to get the CLR to only allocate the used segments?
If these are "sparse" arrays, i.e., if almost all array entries are entries, the simplest solution might be to replace them with dictionaries:
Public Temp(1000000) As Double ' Old
Public Temp As New Dictionary(Of Int32, Double)() ' New
Assignment would be source-code compatible:
Temp(10) = 2.0 ' Works for arrays and dictionaries
Reading would be source-code compatible, if the value is present:
Dim x = Temp(3) ' Works for arrays and dictionary, if Temp(3) has been assigned
However, accessing values which have not been assigned will yield a KeyNotFoundException. If you need that, you'd have to use a "default value dictionary" instead of a regular dictionary. Unfortunately, no such dictionary is built-in in the BCL, but there are others who have already looked at that problem and shared their implementation. If you want to use a plain .NET dictionary, you will need to replace every read access with a method call.
I understand arrays. I know some java from 15 years ago, and I know about classes, objects, instances, variables, static variables and constructors. Not so familiar with these things in VB.
I don't understand object collections..
Suppose I draw a listbox, and name it lstbox1
I see that I can say lstbox1.items.item(0) or lstbox1.items(0)
The fact that I can say listbox1.items(0) puzzles me a little bit. If an object collection is not an array, then it's clearly not an object either.
This link https://msdn.microsoft.com/en-us/library/yb7y698k(v=vs.90).aspx says Collection is an object.
But then what is items(0) items is not a class so that can't be calling a constructor... and items is not a method, it's a property that is an instance of object collection, so I can't see how the (0) works.. I know what it refers to the first object, the element with index 0, but I don't understand how that can work. I know blah(0) would work if blah was an array. And I am sure lstbox1.items is not an array of object collections it's just 1 object collection.
Is it a data structure like an Array, that has its own syntax.. for example one can say dim blah as Integer() or dim blah() as Integer and thus define it without even stating the class Array. Is ObjectCollection a bit like that? It does seem to allow (index) after an instance of it.
VB has a concept called Default Properties. In the case of an ItemCollection type (and a number of other types, as well), the Item property is the default property for the collection. This allows you to use the shorthand from the question.
It's basically just a bit of syntactical sugar. When you say, lstbox1.items(0), it's just shorthand for lstbox1.items.item(0).
Also, don't mistake the various collection types for simple arrays. They will have similar syntax, but every collection type has it's own quirks and use cases, and it's generally worth your time to look at the documentation for the specific type you're working with. Don't assume something is an array, just because you can access the items by index.
Think about this just as a "language feature". This is what really called indexer property. It is implemented as default property in vb.net. In c# implementation is different. The data structure behind it could be anything you want - array, list, dictionary, hashtable. The fact is - it lets you access something by supplying a parameter without calling property syntax. myParentObject(1) instead of myParentObject.GetChildObject(1)
In VB, default property must be indexed.
listbox1 . Items . item(0) --> listbox1 - main object that has property Items , which is a collection. This collection has property item, which is default property or indexer. Item is a property exposing single object from underlying collection.
I am passing a few optional arguments to a function as a tuple, since all of these have to be passed together or not at all. I would like to be able to iterate over the elements of the tuple numerically, and perform an operation on each item. For example:
Public Function myFunction(Optional t As Tuple(Of Integer, String, SomeType) = Nothing) As Integer
For i = 0 to 2
someCollection(i).someMethod(t(i)) 'Pseudocode for accessing ith item in tuple
Next
End Function
One way to resolve the problem would be to use a list, but then I lose the ability to enforce the number of members (which will always be fixed) and the types of each member. Another way would be to write out the statement three times with t.Item1, t.Item2 etc, but this is ugly.
Is there any way to access the nth item in a tuple?
Note: I would like to accomplish this with a tuple if at all possible, even though I am aware I could create alternate method signatures.
(Sure, I’ll turn this into an answer!)
You can put the items into an array for convenience; maintaining the type isn’t really an issue at that point, since if you’re doing the same thing with all of them they need to have some sort of common base class or interface.
Dim a() As Object = {t.Item1, t.Item2, t.Item3}
Then just iterate over that.
In C# if I have the following object:
IEnumerable<Product> products;
and if I want to get how many elements it contains I use:
int productCount = products.Count();
but it looks like there is no such method in VB.NET. Anybody knows how to achieve the same result in VB.NET?
Count is available in VB.NET:
Dim x As New List(Of String)
Dim count As Integer
x.Add("Item 1")
x.Add("Item 2")
count = x.Count
http://msdn.microsoft.com/en-us/library/bb535181.aspx#Y0
In later versions of .net, there is an extension method called Count() associated with IEnumerable<T>, which will use IList<T>.Count() or ICollection.Count() if the underlying enumerator supports either of those, or will iteratively count the items if it does not.
An important caveat not always considered with this: while an IEnumerable<DerivedType> may generally be substituted for an IEnumerable<BaseType>, a type which implements IList<DerivedType> but does not implement ICollection may be efficiently counted when used as an IEnumerable<DerivedType>, but not when cast as IEnumerable<BaseType> (even though the class would support an IList<DerivedType>.Count() method which would return the correct result, the system wouldn't look for that--it would look for IList<BaseType> instead, which would not be implemented.
In general, IEnumerable won't have a Count unless the underlying collection supports (eg List).
Think about what needs to happen for a generic IEnumerable to implement a Count method. Since the IEnumerable only executes when data is requested, in order to perform a Count, it needs to iterate through till the end keeping track of how many elements it has found.
Generally, this iteration will come to an end but you can setup a query that loops forever. Count is either very costly time-wise or dangerous with IEnumerable.
I'm really sorry. This must seem like an incredibly stupid question, but unless I ask I'll never figure it out. I'm trying to write a program to read in a csv file in Visual Basic (tried and gave up on C#) and I asked a friend of mine who is much better at programming than I am. He said I should create a class to hold the data that I read in.
The problem is, I've never created a class before, not in VB, Java, or anything. I know all the terms associated with classes, I understand at a high level how classes work no problem. But I suck at the actual details of making one.
So here's what I did:
Public Class TsvData
Property fullDataSet() As Array
Get
Return ?????
End Get
Set(ByVal value As Array)
End Set
End Property
End Class
I got as far as the question marks and I'm stuck.
The class is going to hold a lot of data, so I made it an array. That could be a bad move. I don't know. All i know is that it can't be a String and it certainly can't be an Integer or a Float.
As for the Getter and Setter, the reason I put the question marks in is because I want to return the whole array there. The class will eventually have other properties which are basically permutations of the same data, but this is the full set that I will use when I want to save it out or something. Now I want to return the whole Array, but typing "Return fullDataSet()" doesn't seem like a good idea. I mean, the name of the property is "fullDataSet()." It will just make some kind of loop. But there is no other data to return.
Should I Dim yet another array inside the property, which already is an array, and return that instead?
Instead of writing your own class, you could get yourself familiar with the pre-defined class System.Data.DataTable and then use that for holding CSV data.
In the last few years that I've been programming, I've never actually used a multi-dimensional array, and I'd advise you not to use them, either. There's usually ways of achieving the same with a better data structure. For example, consider creating a class (let's call it CsvRecord) that holds only one record; that is, only one line from the CSV file. Then use any of the standard collection types from the System.Collections.Generic namespace (e.g. List(Of CsvRecord)) to hold the entire data (ie. all lines) in the CSV file. This effectively reduces the problem to, "How do I read in one line of CSV data?"
If you want to take suggestion #2 even further, do as cHao says and don't simply lay out the information you've read as a CsvRecord; instead, create an object that reflects the actual content. For example, if your CSV file contains product–price information, call your CSV record class ProductInfo or something more fitting.
If, however, you want to go on with your current approach, you will need a backing field for the property, as demonstrated by Philipp's answer. Your property then becomes a "façade" that only delegates to this backing field. This is not absolutely necessary: You could simply make the backing field Public and let the user of your class access it directly, though that is not considered a good practice.
Ideally, you ought to have a class representing the specific data you want to read in. Setting an entire array at once is asking for trouble; some programs that read {C,T}SV files will freak out if all rows don't have the same number of columns, which is exceedingly easy to do if you can set the data to be an array of arbitrary length.
If you're trying to represent arbitrary data, frankly, you'd do just as well to use a List(Of String). If it's meant to be a table, you could instead read in the first line and make it a list as above (let's call it "headers"), and then make each row a Dictionary(Of String, String). (Let's call each row "row", and the collection (a list of these dictionary objects) "rows".) Just read in the line, split it like you did the first, and say something like row(headers(column number)) = value for each column, and then stuff it into 'rows'.
Or, you could use the data classes (System.Data.DataTable and System.Data.DataSet would do wonders here).
Usually you use a private member to store the actual data:
Public Class TsvData
Private _fullDataSet As String()
Public Property FullDataSet() As String()
Get
Return _fullDataSet
End Get
Set(ByVal value As String())
_fullDataSet = value
End Set
End Property
Note that this is an instance of bad design since it couples a concept to a concrete representation and allows the clients of the class to modify the internals without any error checking. Returning a ReadOnlyCollection or some dedicated container would be better.