Type-safe delegate to property getter - vb.net

Suppose I have this simple class:
Public Class Person
Public Property Name() As String
Public Property Married() As Boolean
End Class
I want to create a delegate to its property getters. After a bit of search, including this, I've written this code in VB.NET:
Dim p As New Person()
p.Name = "Joe"
p.Married = True
Dim propGetter1 As Func(Of Boolean) = Function() p.Married
Dim propGetter2 As Func(Of String) = Function() p.Name
And retrieve the value of the properties as follows:
Dim married as Boolean = propGetter1.Invoke
Dim name as String = propGetter2.Invoke
It works fine, but contrary to the example in C# from the link above, it does not check at compile-time the type returned by the Function, so I could just write this code:
Dim propGetter1 As Func(Of Boolean) = Function() p.Name
Getting an error at runtime.
I've also tried this code:
Dim propGetter3 As Func(Of String) = Function(p As Person) p.Name
But I get the following error:
Nested function does not have a signature that is compatible with delegate 'System.Func(Of Boolean)'
So, how can I write this delegates with type-checking at compile-time, just like in C#??
EDIT:
As I've been asked to do so, I explain what I'm trying to do with this approach. I have an application that reads the state of a system (probe mesures, flows, preassures...) each few seconds, and stores this information in some properties of the objects that model the system.
I want to offer the user a list of all the objects defined which have properties that get their values updated this way. Therefore, the user can set alarm rules, so when a property reaches a certain value, an alarm is fired.
In orden to do so, I need to save delegates to the chosen properties, so I can check its values each few seconds, and compare them to the expected ones.
Does it make sense?

This is VB.NET's in/famous dynamic typing at work:
Option Strict Off
Module Module1
Sub Main()
Dim p As New Person With {.Name = "True"}
Dim propGetter1 As Func(Of Boolean) = Function() p.Name
Dim result = propGetter1() '' Fine :)
End Sub
End Module
If you don't like surprises like this then just use Option Strict On and the compiler will tell you that you got it wrong at build time. Use Tools + Options, Projects and Solutions, VB Defaults to change the default setting. Do beware that you might have some trouble with older projects ;)

Related

'Public member 'Find' on type 'MongoCollectionImpl(Of BsonDocument)' not found.'

I am trying to find a particular user from a mongodb collection that matches the given id. Folliwng is my VB.Net code. However, I keep getting the error 'Public member 'Find' on type 'MongoCollectionImpl(Of BsonDocument)' not found.'
Public Function GetCollectionByName(ByVal collectionName As String)
Dim db As IMongoDatabase = DBcontext()
Dim collection As IMongoCollection(Of BsonDocument)
collection = db.GetCollection(Of BsonDocument)(collectionName)
Return collection
End Function
Public Function GetUser(ByVal id As String)
Dim filter = Builders(Of BsonDocument).Filter.Eq(Of String)("ID", id)
Dim collection = GetCollectionByName("Users")
Dim list = collection.Find(filter).ToList()`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ERROR here
Return list
End Function
Firstly, you must have Option Strict Off for that code to even compile. That's bad. You should immediately turn Option Strict On in the project properties and address all the issues it raises. One of those will be the fact that your GetCollectionByName has no return type declared. That means that here:
Dim list = collection.Find(filter).ToList()
that collection variable is implicitly type Object and you are relying on late binding when calling that Find method because the Object class has no such method. As a result, you get no help from Intellisense and Intellisense would have told you what members were and were not available if you were doing this properly.
Regardless, you still could have made it work if you had actually read the documentation for the types you're using to see what members they have. Here is the documentation for the interface you're using in that GetCollectionByName method and, I don't know about you but I don't see any Find method listed there. There is a FindSync method, so maybe that's what you actually want. If you had Option Strict On and used proper types every where, Intellisense would have shown you that.
You should also turn Option Strict On in the IDE options, so that it is On for all future projects.
I had a look at some documentation for the MongoCollectionImpl for Java and there appears to be a find method there but that doesn't necessarily mean that the same method is available in .NET and you aren't working directly with that class anyway. You are working with the IMongoCollection so you should only be working with members of that interface. Basically, your code would need to look more like the below with Option Strict On:
Public Function GetCollectionByName(ByVal collectionName As String) As IMongoCollection(Of BsonDocument)
Dim db As IMongoDatabase = DBcontext()
Dim collection As IMongoCollection(Of BsonDocument)
collection = db.GetCollection(Of BsonDocument)(collectionName)
Return collection
End Function
Public Function GetUser(ByVal id As String) As List(Of BsonDocument)
Dim filter = Builders(Of BsonDocument).Filter.Eq(Of String)("ID", id)
Dim collection = GetCollectionByName("Users")
Dim list = collection.FindSync(filter).ToList()
Return list
End Function
You may want to declare the GetUser method as type IList(Of BsonDocument) if you want to work with interfaces. You probably ought to rename that method or change the implementation too. If a method is returning a list then the name should not indicate that it returns a single item.

How to mimic Java's Wildcard types in VB.net?

I have an interface which I defined like this:
Public Interface ISomething(Of T)
' methods
End Interface
I now did an implementation:
Public Class ConcreteThing
Implements ISomething(of SomeClass)
' Implementation
End Class
I have multiple such concrete implementations, and want to have a function which returns any of them based on its parameters. In Java, I would do something like this:
public ISomething<?> getSomething(ParamType p) {
if(p.hasFoo()) return new ConcreteThing();
if(p.hasBar()) return new OtherConcreteThing();
throw new IllegalStateException("p neither has Foo nor Bar");
}
I already searched about this issue and found out that VB.net does not have wildcard types, so I tried:
Public Function GetSomething(p as ParamType) as ISomething(Of Object)
If p.HasFoo Then Return New ConcreteThing()
If p.HasBar Then Return New OtherConcreteThing()
Throw New InvalidOperationException("p neither has Foo nor Bar")
End Function
This compiles, but I get the warning: Runtime errors might occurr when converting 'Foo.ConcreteThing' to 'Foo.ISomething(Of Object)'.
When I try the following, as suggested in a similar question:
Public Function GetSomething(Of T)(p as ParamType) as ISomething(Of T)
If p.HasFoo Then Return New ConcreteThing()
If p.HasBar Then Return New OtherConcreteThing()
Throw New InvalidOperationException("p neither has Foo nor Bar")
End Function
the warning only changes to Runtime errors might occurr when converting 'Foo.ConcreteThing' to 'Foo.ISomething(Of T)'.
So, how do I get this right? Or, if this indeed IS right, how do I have Visual Studio ignore this warning?
I investigated on this issue a little more, discussed it with my colleagues, and I think I found the solution / reason for the warnings.
The warning message is a bit hard to understand and unconcise. What they are trying to say is that, as silly as it sounds, covariance does not work as expected for primitive types, even when using the Out keyword!
Consider an excerpt from this example on MSDN:
' Covariance.
Dim strings As IEnumerable(Of String) = New List(Of String)()
' An object that is instantiated with a more derived type argument
' is assigned to an object instantiated with a less derived type argument.
' Assignment compatibility is preserved.
Dim objects As IEnumerable(Of Object) = strings
This works. Now, change the first IEnumerable to IList:
Dim strings As IList(Of String) = New List(Of String)()
Dim objects As IEnumerable(Of Object) = strings
Works, too. OK, we are lucky, let's change the second:
Dim strings As IList(Of String) = New List(Of String)()
Dim objects As IList(Of Object) = strings
Boom, InvalidCastException. Looking at the signature, this is because the generic parameter in IEnumerable is defined as Of Out T, and IList is only defined As T.
Now, let's define our own.
Interface ISomething(Of Out T)
ReadOnly Property Value As T
End Interface
Class IntThing
Implements ISomething(Of Integer)
Public ReadOnly Property Value As Integer Implements ISomething(Of Integer).Value
Get
Return 42
End Get
End Property
End Class
Now, do this:
Dim s1 As ISomething(Of Integer) = new IntThing()
Works. Now add this:
Dim s2 As ISomething(Of Object) = s1
Boom, InvalidCastException. Now, the funniest part. Add a second implementation of ISomething:
Class StringThing
Implements ISomething(Of String)
Public ReadOnly Property Value As String Implements ISomething(Of String).Value
Get
Return "foo"
End Get
End Property
End Class
And do:
Dim s1 As ISomething(Of String) = New StringThing()
Dim s2 As ISomething(Of Object) = s1
This, on the other hand, works! So, let's go back to the List example.
Dim ints As IEnumerable(Of Integer) = New List(Of Integer)()
Dim objects As IEnumerable(Of Object) = ints
This will get you an InvalidCastException, too.
So, my conclusion is that covariance not only needs the Out keyword, it additionally only works with non-primitive types. .net seems to handle wrapper classes differently to the JVM.
So, never ignore this warning when it pops up. When it does, things will go wonky in an absolutely illogical way! That means, for what I want to achieve, going with simple Objects instead trying to find an equivalent for ISomething<?> is the way to go.
I only use this internally to read a binary file into a more convenient structure to extract the data I pass out via the API in the end, so using Object does not make things very much worse here.
It's weird, I don't get the warning like you do. But I do get an InvalidCastException if I try to run the code.
To get rid of the error (and hopefully your warning as well), you can make the generic type T on ISomething covariant.
Public Interface ISomething(Of Out T) ' Add the "Out" keyword here to make it covariant
' methods
End Interface
Then you should be able to use your GetSomething function as you had attempted:
Public Function GetSomething(p as ParamType) as ISomething(Of Object)
If p.HasFoo Then Return New ConcreteThing()
If p.HasBar Then Return New OtherConcreteThing()
Throw New InvalidOperationException("p neither has Foo nor Bar")
End Function
Relevant documentation: Covariance and Contravariance in Generics
Covariance
Enables you to use a more specific type than originally specified.
You can assign an instance of IEnumerable<Derived> (IEnumerable(Of Derived) in Visual Basic) to a variable of type IEnumerable<Base>.
And lower in the Defining Variant Generic Interfaces and Delegates section:
A covariant type parameter is marked with the out keyword (Out keyword in Visual Basic, + for the MSIL Assembler).

How can I get a property name for a type without the need to instantiate an object of that type?

I have a requirement where I need to have a "type safe" way of accessing property names, without actually instantiating an object to get to the property. To give an example, consider a method that takes as arguments a list of IMyObject and a string that represents a property name (a property that exists in IMyObject).
The methods implementation will take the list and access all the objects in the list using the property name passed... for some reason or another, we won't dwell on that!!
Now, I know that you can do this using an instantiated object, something like ...
Dim x as MyObject = nothing
Dim prop As PropertyInfo = PropHelper.GetProperty(Of MyObject)(Function() x.MyProperty)
Where my helper method uses reflection to get the name of the property as a string - there are numerous examples of this flying around on the web!
But I don't want to have to create this pointless object, I just want to do something like MyObject.MyProperty! Reflection allows you to iterate through a types properties and methods without declaring an object of that type... but I want to access a specific property and retrieve the string version of its name without iteration and without declaring an object of that type!
The main point here is that although I am trying to get the property name as a string... this is done at run time... at compile time, I want this to be type safe so if someone changes the property name, the compilation will break.
Can anyone help in this quest!?!
So here is a quick code-listing to demonstrate the answer that I was looking for:
Imports System.Linq.Expressions
Public Class A
Public Prop1 As String
Public Prop2 As Integer
End Class
Public Class Form1
Public Function GetPropertyNameB(Of TModel, TProperty)(ByVal [property] As Expression(Of Func(Of TModel, TProperty))) As String
Dim memberExpression As MemberExpression = DirectCast([property].Body, MemberExpression)
Return memberExpression.Member.Name
End Function
Public Sub New()
InitializeComponent()
Dim propertyName As String = GetPropertyNameB(Function(myObj As A) myObj.Prop1)
Dim propertyName2 As String = GetPropertyNameB(Function(myObj As A) myObj.Prop2)
MsgBox(propertyName & " | " & propertyName2)
End
End Sub
End Class
You may be able to pass the property in as a simple lamdba expression, and take it in the method as an expression tree. You should be able to analyze the expression tree to get the string name of the property, but it the lambda expression will fail to compile if the property name changes. Check out this page for more details:
http://msdn.microsoft.com/en-us/library/bb397951.aspx
You can make use of the NameOf function:
Dim fieldName = nameOf(MyClass.MyField)

Problem returning object from VB.NET COM Assembly into VBA (Access)

I have an assembly in VB .NET 2.0 that I am trying to use to call a webservice.
This will be COM visible, and return the results to Access in VBA.
The .NET Assembly passes all tests and executes perfectly.
I was experiencing "Object does not support this property or method" errors when calling the methods from VBA.
I broke it down to a certain object that was being returned and added some test methods to the .NET DLL.
There is a "Patient" object I want to return.
It looks like this (made it very very simple to test it):
Option Strict On
Option Explicit On
<ComClass(Patient.ClassId, Patient.InterfaceId, Patient.EventsId)> _
Public Class Patient
#Region "COM GUIDs"
' These GUIDs provide the COM identity for this class
' and its COM interfaces. If you change them, existing
' clients will no longer be able to access the class.
Public Const ClassId As String = "672dfbd9-8f3a-4ba2-a33d-89fef868f2b9"
Public Const InterfaceId As String = "74a9c54c-4427-4d31-8220-3258ecda345d"
Public Const EventsId As String = "dc25515e-1bb7-4a66-97d5-270c00d792a9"
#End Region
Public Sub New()
MyBase.New()
End Sub
Public Property StorePatientID() As Integer
Get
Return m_StorePatientID
End Get
Set(ByVal value As Integer)
m_StorePatientID = value
End Set
End Property
Private m_StorePatientID As Integer
End Class
So about as simple as an object can be really.
I have a method that just returns a dummy record, just to test it:
Public Function GetPatientTest() As Patient
Dim patient As New Patient
patient.StorePatientID = 99
Return patient
End Function
This fails with the afformentioned error.
HOWEVER,
This method succeeds!
Public Function GetPatientArrayTest() As Patient()
Dim strings As New List(Of Patient)
Dim patient As New Patient
patient.StorePatientID = 99
strings.Add(patient)
Return strings.ToArray
End Function
The DLL is made com visible through "Properties" page.
Builds to project/bin/debug, always do a rebuild.
Always seems to be updated with new methods etc when I look at it in VBA so don't think it's looking at an old version.
Obviously no funny dependencies with these methods.
Really really struggling with this.
EDIT:
Update 16/03/2011 - Added VBA script
Public Function FindPatientsTest(ByVal surname As String, ByVal surnameBeginsWith As Boolean, ByVal forename As String, ByVal forenameBeginsWith As Boolean, ByVal dateOfBirth As String)
Dim token As String
token = Login()
Dim patient As SCIStoreWS60.patient
Set patient = New SCIStoreWS60.patient
'// This doesn't work.
'// When adding a "Watch" to the function, I can see it returns an "Object/Patient" and is the correct results
'// When adding a "Watch" to the variable "patient" I can see it is a "Patient/Patient"
patient = sciStore.GetPatientTest()
'// This works fine
Dim something As Variant
something = sciStore.GetPatientArrayTest()
End Function
Update 16/03/2011 5 minutes later - Chastising myself
Sorry, I just worked it out.
I need to "Set" the patient variable.
Set patient = sciStore.GetPatientTest()
Why didn't I need to do this for the "something" variant?
So, yes, you need to Set object references, but not arrays.

How to write a simple Expression-like class in .NET 2.0?

I'm currently working in .NET 2.0 Visual Basic. The current project is an Active Directory Wrapper class library within which I have a Searcher(Of T) generic class that I wish to use to search the underlying directory for objects.
In this Searcher(Of T) class I have the following methods:
Private Function GetResults() As CustomSet(Of T)
Public Function ToList() As CustomSet(Of T)
Public Function Find(ByVal ParamArray filter() As Object) As CustomSet(Of T)
// And some other functions here...
The one that interests me the most is the Find() method to which I can pass property and values and would like to parse my LDAP query from this filter() ParamArray parameter. Actually, all I can figure out is this:
Public Sub SomeSub()
Dim groupSearcher As Searcher(Of Group) = New Searcher(Of Group)()
Dim groupsSet as CustomSet(Of Group) = groupSearcher.Find("Name=someName", "Description=someDescription")
// Working with the result here...
End Sub
But what I want to be able to offer to my users is this:
Public Sub SomeSub()
Dim groupSearcher As Searcher(Of Group) = New Searcher(Of Group)()
Dim groupsSet As CustomSet(Of Groupe) = groupSearcher.Find(Name = "someName", Guid = someGuid, Description = "someDescription")
// And work with the result here...
End Sub
In short, I want to offer some kind of Expression feature to my users, unless it is too much work, as this project is not the most important one and I don't have like 2 years to develop it. I think that the better thing I should do is to write something like CustomExpression that could be passed in parameters to some functions or subs.
Thanks for any suggestions that might bring me to my goal!
Interesting question. This is a language dependent feature, so I don't see this happening without some clever trickery of the IDE/compiler.
You could however have optional overloads on your Find method (vb.net is good for this), then make the search string manually to obtain the result.
Finally you could make use of lambda functions, but only in .net 3.5 and above. Even still, it would require your searcher to expose a preliminary set of data so you can recover the expression tree and build up the find string.
UPDATE
I've just been playing around with Reflection to see if I can retrieve the parameters passed, and build up a string dynamically depending on if they exist. This doesn't appear to be possible, due to the fact that compiled code doesn't reference the names.
This code just used was:
'-- Get all the "parameters"
Dim m As MethodInfo = GetType(Finder).GetMethod("Find")
Dim params() As ParameterInfo = m.GetParameters()
'-- We now have a reference to the parameter names, like Name and Description
Hmm. http://channel9.msdn.com/forums/TechOff/259443-Using-SystemReflection-to-obtain-parameter-values-dynamically/
Annoyingly it's not (easily) possible to recover the values sent, so we'll have to stick with building up the string in a non-dynamic fashion.
A simple optional method would look like:
Public Sub Find( _
Optional ByVal Name As String = "", _
Optional ByVal Description As String = "")
Dim query As String = String.Empty
If Not String.IsNullOrEmpty(Name) Then
query &= "Name=" & Name
'-- ..... more go here with your string seperater.
End If
End Sub