Error trying to sum values of a dictionary - vb.net

I'm trying to sum all values of a dictionary(of String, Boolean) as explained in THIS answer but I'm getting error:
Overload resolution failed because no accessible 'Sum' accepts this
number of type arguments
I also tryed on .netFiddle with this:
Imports System.Linq
imports system.collections.generic
Public Module Module1
Public Sub Main()
Dim a as integer
Dim Dic As new dictionary(of string, boolean) _
from {{"First", 0},{"Second",0},{"Third",1}}
a = Dic.values.Sum()
End Sub
End Module
And get same error.
What am I missing?
EDIT:
I know that the code will work if I change my dictionary(of string, boolean) into a dictionary(of string, integer) but I'd like to know if I can use sum() to check boolean values or not.

Sum is not the correct method to use for counting how many entries are true in your dictionary. Sum requires an integer to act on. Your link works correctly because the dictionary has values of type integer, you have a boolean type.
If you want to count how many entries in the dictionary are true then you should use Where to enumerate the entries with True value and then Count the result
Public Sub Main()
Dim a as integer
Dim Dic As new dictionary(of string, boolean) _
from {{"First", 0},{"Second",0},{"Third",1}}
a = Dic.Values.Where(Function(x) x = True).Count()
End Sub
or also
a = Dic.AsEnumerable().Count(Function(x) x.Value = True)
Notice that x = True and x.Value = True are not necessary. Writing x or x.Value is enough. Added here just to clarify the intentions

Related

Does it matter if the object of List(T) is being passed ByVal or ByRef

In the following example, does it matter if i pass the List(T) object in both functions ByRef or ByVal?
Is this right that as List is a reference type so value will always be changes even if i pass the object ByVal.
Would it be better if i pass the object byRef in function "ListChanged" as list is being updated.
Public Class MyClass_
Public Sub TestMethod()
Dim List_1 As New List(Of Integer)()
Dim List_2 As New List(Of Integer)()
List_1.Add(100)
List_2.Add(50)
List_1 = ActualListNotChanged(List_1) '---101
List_2 = ListChanged(List_2) '---50,51
End Sub
Private Function ActualListNotChanged(ByVal lst As List(Of Integer)) As List(Of Integer)
Dim nList As New List(Of Integer)()
For Each item As Integer In lst
If item <> 50 Then
nList.Add(101)
End If
Next item
Return nList
End Function
Private Function ListChanged(ByVal lst As List(Of Integer)) As List(Of Integer)
lst.Add(51)
Return lst
End Function
End Class
In your example, ByVal (the default) is the most appropriate.
Both ByVal and ByRef allow you to modify the list (e.g. add/remove items).
ByRef also allows you to replace the list with a different list, e.g.
Dim List1 As New List(Of Int)
List1.Add(1)
ListReplacedByVal(List1)
' List was not replaced. So the list still contains one item
Debug.Assert(List1.Count = 1) ' Assertion will succeed
ListReplacedByRef(List1)
' List was replaced by an empty list.
Debug.Assert(List1.Count = 0) ' Assertion will succeed
Private Sub ListReplacedByVal(ByVal lst As List(Of Integer))
lst = New List(Of Int)
End Sub
Private Sub ListReplacedByRef(ByRef lst As List(Of Integer))
lst = New List(Of Int)
End Sub
In general you should use ByVal. The object you pass can be modified (in the sense that you can call its methods and property setters to change its state). But it can't be replaced by a different object.
I'd say best practise would be to pass using ByRef if (and only if) you are changing the list. Not a long answer, but it's short and sweet!

How to resolve errors when using 'Dictionary'

I'm creating a Class to handle registry entries for my application, but I'm getting some problems early on.
In the below, it should take all of the keys/values for a SubKey and add them to a Dictonary. The message box that is commented out shows the keys and values correctly, but every time the function is run the line below generates an error A first chance exception of type 'System.NullReferenceException'.
As the registry keys themselves seem to be fine, I think it's to do with the way I'm using the RegKeys Dictonary. If someone could take a look and advise I'd be grateful. Thanks.
This is how I'm initiating the Class (I haven't even tried to do anything else just yet) -
Private Sub getMyRegSettings()
Dim ServerPing As RegistryKey = Registry.CurrentUser.OpenSubKey("ServerPing")
Dim Servers As RegistryKey = ServerPing.OpenSubKey("Servers")
Dim MyRegistry As New MyRegistry(Servers)
Dim RegKeys As Dictionary(Of String, String) = MyRegistry.RegKeys
End Sub
And here is the Class that is causing me some trouble -
Public Class MyRegistry
Public RegKeys As Dictionary(Of String, String)
Public Sub New(SubKey As RegistryKey)
get_registry_keys(SubKey)
End Sub
Private Sub get_registry_keys(SubKey As RegistryKey)
' Print the information from the Test9999 subkey.
For Each valueName As String In SubKey.GetValueNames() ' Error occurs here
'MsgBox("Key: " & valueName & vbCrLf & "Value: " & SubKey.GetValue(valueName))
RegKeys(valueName) = SubKey.GetValue(valueName).ToString()
Next valueName
End Sub
End Class
You are not initialising your RegKeys Object
Try changing this line:
Public RegKeys As Dictionary(Of String, String)
to this:
Public RegKeys As New Dictionary(Of String, String)
This will ensure the dictionary is initialised when the class is created
This line
Public RegKeys As Dictionary(Of String, String)
declares a variable of Dictionary type (a reference type) but this variable is not an instance of a Dictionary. To become a valid instance you need to instantiate with new
Public RegKeys = new Dictionary(Of String, String)

Vb.Net Using Dictionaries with Excel Ranges as Keys

The following code is excerpted from a larger procedure (the surrounding code is not relevant). Can anyone explain why I am unable to get the second ContainsKey line to return True? Hint: try this on a worksheet with just a few populated cells to reduce looping.
For Each ws As Excel.Worksheet In Wb.Worksheets
Dim dic As New Dictionary(Of Excel.Range, String)
rngUsed = ws.UsedRange
For Each cell As Excel.Range In rngUsed
dic.Add(cell, "test")
'THE FOLLOWING TWO MESSAGES SHOULD DISPLAY THE SAME RESULT, BUT DO NOT. WHY???
MsgBox(dic.ContainsKey(cell)) 'Returns True
MsgBox(dic.ContainsKey(ws.Range(cell.Address))) 'Returns False
Next
Next
UPDATE: I have added the following code and it seems to be working:
Dim dic As New Dictionary(Of Excel.Range, String)(New MyComparer()) 'replaces line from above
Class MyComparer
Implements IEqualityComparer(Of Excel.Range)
Public Function Equals1(ByVal x As Excel.Range, ByVal y As Excel.Range) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of Excel.Range).Equals
If x.Address(External:=True) = y.Address(External:=True) Then
Return True
Else
Return False
End If
End Function
Public Function GetHashCode1(ByVal obj As Excel.Range) As Integer Implements System.Collections.Generic.IEqualityComparer(Of Excel.Range).GetHashCode
Return obj.Count.GetHashCode
End Function
End Class
When an object is used as the key for the dictionary, .Net uses the GetHashCode to generate the key that is used in the underlying hashtable. Since you are using two different objects, you will get different values.
See the MSDN documentation for more details.
A better approach would be to turn the range into a string representation and use that as the key.
Dim dic As New Dictionary(Of Excel.Range, String)(New MyComparer()) 'replaces line from above
Class MyComparer
Implements IEqualityComparer(Of Excel.Range)
Public Function Equals1(ByVal x As Excel.Range, ByVal y As Excel.Range) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of Excel.Range).Equals
If x.Address(External:=True) = y.Address(External:=True) Then
Return True
Else
Return False
End If
End Function
Public Function GetHashCode1(ByVal obj As Excel.Range) As Integer Implements System.Collections.Generic.IEqualityComparer(Of Excel.Range).GetHashCode
Return obj.Count.GetHashCode
End Function
This is the solution. Note that GetHashCode used in this custom comparer is very slow, so if anyone has an idea to speed this up, I'd love to hear it. #competent_tech, I have to use objects for the keys, since there is no string representation for a range that is unique and not subject to change (like how an address changes when adding/deleting rows, for example).

Pass property to access using .NET

I'm fairly sure this is possible, but what I want to do is have a generic method where I can pass in an object along with a Expression that will tell the method which Property to use in it's logic.
Can anyone get me started on the syntax for something like this?
Essentially what I would like to code is something like:
Dim firstNameMapper as IColumnMapper = new ColumnMapper(of Author)(Function(x) x.FirstName)
Dim someAuthorObject as new Author()
fistNameMapper.Map("Richard", someAuthorObject)
Now the mapper object would know to set the FirstName property to "Richard".
Now using a Function here won't work, I know this... I'm just trying to give an idea what I'm trying to work towards.
Thanks for any help!
You could use expression trees to implement this behavior, but it would be a lot simpler to pass the ColumnMapper a slightly different function. Instead of using expression that reads the property, you could give it a function that sets the value of the property:
Dim firstNameMapper as IColumnMapper = _
new ColumnMapper(of Author)(Sub(x, newValue) _
x.FirstName = newValue _
End Sub)
I think this syntax is new in Visual Studio 2010 (but I'm not a VB expert). Anyway, the type of the parameter would be Action<Author, string> and you could simply invoke it anytime you needed from the ColumnMapper to set the property.
Using expression trees, you'd have to construct expression that sets the property and compile it at runtime, so I think the additional few bits of code above are easier way to solve the problem.
Okay, so I have implemented an analogous solution (i'm not using 2010 so I can't use Tomas' solution directly) but although it compiles, the property does not seem to be set. So here are all the pieces:
Module Module1
Sub Main()
Dim inputSource() As String = {"Richard", "Dawkins"}
Dim firstNameMapper As New ColumnMapper(Of Author)(Function(obj, value) obj.FirstName = value, 0)
Dim lastNameMapper As New ColumnMapper(Of Author)(Function(obj, value) obj.LastName = value, 1)
Dim theAuthor As New Author
firstNameMapper.map(inputSource, theAuthor)
lastNameMapper.map(inputSource, theAuthor)
System.Console.WriteLine(theAuthor.FirstName + " " + theAuthor.LastName)
System.Console.ReadLine()
End Sub
End Module
Public Class ColumnMapper(Of T As {Class})
Dim _propertyMapper As Action(Of T, String)
Dim _columnIndex As Int32
Public Sub New(ByVal mapAction As Action(Of T, String), ByVal columnNumber As Int32)
_propertyMapper = mapAction
_columnIndex = columnNumber
End Sub
Public Sub map(ByVal sourceFields As String(), ByRef destinationObject As T)
_propertyMapper(destinationObject, sourceFields(_columnIndex))
End Sub
End Class
Public Class Author
Private _firstName As String
Private _lastName As String
Public Property FirstName() As String
Get
Return _firstName
End Get
Set (ByVal value As String)
_firstName = value
End Set
End Property
Public Property LastName() As String
Get
Return _lastName
End Get
Set (ByVal value As String)
_lastName = value
End Set
End Property
End Class
Any idea why the property is not being set?
Not sure why the solution using inline 'Function' doesn't work. Perhaps someone more versed in the inner workings of vb.net can explain it, but if you implement the main module as below, it works. Thanks Tomas for pointing me in the right direction!
Module Module1
Sub Main()
Dim mapAction As Action(Of Author, String)
Dim inputSource() As String = {"Richard", "Dawkins"}
Dim firstNameMapper As New ColumnMapper(Of Author)(AddressOf setFirstName, 0)
Dim lastNameMapper As New ColumnMapper(Of Author)(AddressOf setLastName, 1)
Dim theAuthor As New Author
firstNameMapper.map(inputSource, theAuthor)
lastNameMapper.map(inputSource, theAuthor)
System.Console.WriteLine(theAuthor.FirstName + " " + theAuthor.LastName)
System.Console.ReadLine()
End Sub
Public Sub setFirstName(ByVal obj As Author, ByVal value As String)
obj.FirstName = value
End Sub
Public Sub setLastName(ByVal obj As Author, ByVal value As String)
obj.LastName = value
End Sub
End Module

Cast array in Object variable to type in System.Type variable

I have this function:
Public Sub DoStuff(ByVal type as System.Type, ByVal value as Object)
End Sub
The 'value' argument is always an array of the same type as 'type'. How can I loop through the values of the array?
I'd like to be able to do something like this:
DoStuff(GetType(Integer), New Integer(){1,2,3})
Public Sub DoStuff(ByVal type as System.Type, ByVal value as Object)
//Strongly types arr as Integer()
Dim arr = SomeCast(type, value)
For Each i in arr
//Do something with i
Next
End Sub
Edit Ok, I think I'll add more details so you can see what I'm trying to do. I have an object that captures values coming back from another page. Once I have them captured, I want to loop through the 'Values' property. So DoStuff() above would be called for each dictionary object in 'Values'. If the value in the dictionary objct is an array I want to loop through it as well. I was saving the type added through the generic function call as a System.Type, but maybe that's not the way to go. How can I write this so I can save the type of the array and loop through the array later?
Public Class PopUpReturnValues
Implements IPopUpReturnValues
Public Sub AddValue(Of T As Structure)(ByVal name As String, ByVal value As T) Implements IPopUpReturnValues.AddValue
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(T), .Value = value, .IsArray = False})
End Sub
Public Sub AddArray(Of T As Structure)(ByVal name As String, ByVal values As T()) Implements IPopUpReturnValues.AddArray
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(T), .Value = values, .IsArray = True})
End Sub
Public Sub AddStringValue(ByVal name As String, ByVal value As String) Implements IPopUpReturnValues.AddStringValue
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(String), .Value = value, .IsArray = False})
End Sub
Public Sub AddStringArray(ByVal name As String, ByVal values As String()) Implements IPopUpReturnValues.AddStringArray
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(String), .Value = values, .IsArray = True})
End Sub
Private _values As New Dictionary(Of String, PopUpReturnValue)
Public ReadOnly Property Values() As IDictionary(Of String, PopUpReturnValue)
Get
Return _values
End Get
End Property
Public Class PopUpReturnValue
Public UnderlyingType As Type
Public Value As Object
Public IsArray As Boolean
End Class
End Class
Comments moved to answers per OP
Your "do something" code in based on the type I assume, String vs Int vs Apple, it would need to handle all three types with an If statement. Just include an overload for those three types, you don't actually need to pass the type information. However, if its just calling ToString() then just use an Object array.
And if you don't like overloads, just use the TypeOf operator to inspect the values of the array. When you throw an Integer into an Object array, its still an Integer, just a boxed one.
Is the type known at compile time? If so, perhaps you could use Generics.
You can provide an Action, like this:
Public Sub DoStuff(ByVal value As Array, ByVal process As Action(Of Object) )
For Each item In value
process(item)
Next item
End Sub
Then you just need a method that takes one parameter for each of the types you care about and knows how to cast object to that type. Then call DoStuff() passing in the address of that method. You could even use a lambda if you wanted.