VB.NET: no warning when returning object that is not an instance of function's return type - vb.net

We have this:
Friend NotInheritable Class ConcreteGraphFactory
Inherits AbstractGraphFactory
Public Shared ReadOnly Instance As New ConcreteGraphFactory()
Private Sub New()
MyBase.New()
End Sub
Friend Overrides Function Create() As AbstractGraph
Return New ConcreteGraph()
End Function
Private NotInheritable Class ConcreteGraph
Inherits AbstractGraph
Private ReadOnly Question1 As New Question("Why isn't this showing a warning?")
Public Overrides Function GetRoot() As IRoot
Return Question1 '<---HERE
End Function
Public Sub New()
End Sub
End Class
End Class
And I have IRoot:
Friend Interface IRoot
Inherits IQuestion
Function GetContainer() As AbstractGraph
End Interface
And finally Question:
Public Class Question
Implements IQuestion
' code....
End Class
Why would VS not show a warning? Question does not implement IRoot...

If you want the compiler to give an error there, then you need to set Option strict to On. You can do that on the Compile tab of the project's Properties. Or add Option Strict On to the top of the file that contains this code.
Here are a few pages that have more details about what Option Strict means.
http://support.microsoft.com/en-us/kb/311329
https://msdn.microsoft.com/en-us/library/zcd4xwzs.aspx
Option Strict Off means that the Visual Basic compiler doesn't enforce strict data typing. It will try to do implicit type conversions and throw run time errors if that can't be done.
I didn't think it had anything to do with IRoot being an interface, but after trying it out it looks like it does. If GetRoot returned a class that Question didn't inherit from, then you would get a compiler error even with Option Strict off.
Running with Option Strict off actually makes some things easier, especially when dealing with late bound COM objects. For the most part, you don't have to worry about type casts when writing code.
However, it's also one of the reasons many people don't like VB.NET. Personally, I liked it when I was working with it, but it's been long enough now that it does seem strange that the compiler wouldn't be doing all the strict type checking for you. I could always tell when some VB code had been generated via a conversion tool from C# because it would have a bunch of DirectCast calls that you wouldn't see in code that a VB developer had written.
When C# came out with the dynamic keyword in 2009, the VB.NET developers were thinking, "Meh. We've always been able to write code without worrying about types." Of course, VB.NET wasn't the same as dynamic in C#, but many of the early dynamic code examples were showing things that you could already do in VB with option strict turned off.

Related

Hide VBA procedures from Excel application but not from other projects

I know this is a long shot, but with the limitations in "Option Private Module" and even worse "Private Sub/Function", does anyone know if there is a way of hiding VBA procedures from the Excel application but not from other projects?
I have an XLAM with a subset of reusable functionality that I like to include and reference from new Excel projects, but using "Option Private Module" hinders this and if I omit it, a bunch of unusable or obscure functions and subs become visible and available to the application.
Convert your standard modules in the XLAM to class modules (set to
Public Not Creatable);
Create an additional Class Module that returns an instance (with a
bit of additional work, the instance) of each such module; and
Create a single standard module with one property that returns the instance of the main class-entry module.
Class1:
Option Explicit
Public Sub IAmInvisible()
End Sub
ModuleEntry:
Option Explicit
Private mClass As New Class1
Public Property Get TheClass() As Class1
Set TheClass = mClass
End Property

Compile time v runtime exceptions

Please see the code below:
Public Class Student
End Class
Public Class Undergraduate
Inherits Student
Public Sub Test(ByVal o As Object, ByVal s As Student)
o.Hello() 'Line 8
s.Hello() 'Line 9
End Sub
End Class
Line 8 throws an exception at runtime i.e. missing member exception. Line 9 produces a compile time error i.e. Hello is not a member of Student. Why doesn't line 8 throw a compile time exception.
Because o could be of type Student at runtime. Every class can be casted to Object.
The last time I used late binding was in C#. I have discovered that Visual Basic seems to be a bit of an exception as described here:
"
Visual Basic uses them whenever the variable is of type Object and the compiler directive "Option Strict Off" is in force
"
Source Wikipedia: http://en.wikipedia.org/wiki/Late_binding#Late_binding_in_.NET.
Public Class Student
Public Overridable Sub Test()
Console.WriteLine("Hello, I am a student.")
End Sub
End Class
Public Class Undergraduate
Inherits Student
Public Overrides Sub Test()
MyBase.Test ' skip this if this class COMPLETELY
' replaces the base class functionality
Console.WriteLine("...and I am an undergrad.")
End Sub
End Class
Dim a As New Student
Dim b As New Undergraduate
a.Test
b.Test
Output:
Hello, I am a student ' from a
Hello, I am a student ' from b's MyBase.Test
...and I am an undergrad. ' from b
Your question relates to late binding. By passing o as Object, the IDE cannot tell whether the Hello method is available on a given object. It might be and it might not be. If you have Option Strict On the default settings will not allow this to compile for just this reason. If you are sure you know what you are doing, you can customize the error to change Late Binding errors to warnings or even ignore them.
The restructured code may give a better view of using inherited classes to avoid needing to use late binding and because passing an object to itself is a little odd.
Because you forgot to turn Option Strict On.
Really, it's the first thing one should do when creating a new VB.NET project, and it's a really pitty that it isn't enabled by default.
A list of which compile-time checks are added with Option Strict On can be found on MSDN:
http://msdn.microsoft.com/en-us/library/zcd4xwzs.aspx

.net assembly does not appear in Excel's References

I'm following Rich Newman's guide to using .net code assemblies in Excel. I have made a small test class called MyPro[p in a project called MyProperty that looks like this:
Imports System.Runtime.InteropServices
Public Class MyProp
Public Function GetData() As String
Return "Hello World"
End Function
End Class
It compiles fine, puts a CLSID into regedit, and (after browsing to find the TLB) allows itself to be added to Excel's References. However, I can't actually use it. I tried this in VBA:
Private Sub test()
Dim test As New MyProperty.MyProp
MsgBox test.GetData()
End Sub
Which returns:
"Class does not support Automation or does not support the expected interface"
I assume that the error means that it can't find GetData or I'm calling it incorrectly. I have re-added the TLB, with no effect.
Any ideas?
The problem has to do with the order of operations in the bindings. You can make this work by DIMming the object then Newing it on a separate line.

Expose .NET DataTable properties to VBA via COM Interface

I am trying to create a .Net DLL basically as an abstraction layer for database connections; it is going to replace a current DLL we have that is written in VB6 and I am trying to match the current functionality as much as possible.
Anyway, the essential issue I am having is that I can't find a way to get .Net classes like DataColumnCollection or DataColumn to display in the VBA Interpreter -- It may say, for example, "Column" with the type "MarshalByValueComponent," but the value will be "No Variables".
I can get it to work if I completely re-create both classes (i.e. Fields as a collection of field, which inherits from DataColumn, and then define an interface for both), but that seems like a lot of added overhead for what (should be?) a pretty simple idea. I feel like I am just missing something very simple with the way the marshaller is handling the DataColumn class.
A lot of the stuff I am finding online is on how to convert a DataTable or DataReader to a legacy ADODB Recordset, but that also would add a lot of overhead... I'd rather leave it as a DataTable and create a COM interface to allow VBA to interact with it; that way if, for example, they want to write the table to an excel sheet, I wouldn't be duplicating work (convert to ADODB recordset, then read/write to excel sheet. You'd need to iterate the entire table twice...)
Sorry for the book-length explanation -- I felt the problem needed a bit of clarification since the root-cause is trying to match legacy functionality. Here is an example of my current interface that does not work:
Public Interface IDataTable
ReadOnly Property Column As DataColumn
End Interface
<ClassInterface(ClassInterfaceType.None)> _
<System.ComponentModel.DesignerCategory("")> _
<ComDefaultInterface(GetType(Recordset.IDataTable))> _
<Guid("E7AFBBB6-CB20-44EC-9CD2-BC70B94CD8B7")> _
Public Class Recordset : Inherits Data.DataTable : Implements IDataTable
Public ReadOnly Property Column As DataColumn Implements IDataTable.Column
Get
Return MyBase.Columns(0)
End Get
End Property
Note: I originally tried the property Columns as DataColumnCollection which returned MyBase.Columns. That came through as an Object, instead of MarshalByValueComponent, but was also empty. I know MyBase.Column(0) has a value, because I can put Msgbox(MyBase.Columns(0).ColumnName) right above the return in the get and it pops up fine (don't judge; this is way easier than using a debugger for this)...
I wouldn't mind just defining them both, but I can't inherit DataColumnCollection and the COM interface already sucks at dealing with generics. Is there any other way around this without re-inventing the wheel?
Thanks for your help!
I just spent the last 3 weeks doing something eerily similar.
I ended up making two .NET assemblies:
A pure .NET assembly that talks to the datastore (for use by .NET apps).
A "COM Interop" assembly that wraps the first assembly and adds the COM overhead (ADODB references and COM-Visible interfaces).
I call the second assembly from Excel VBA using the VSTO "AddIn.Object" property.
I ended up converting System.Data.DataTables to ADODB.Recordsets as you mentioned. Getting .NET and VBA talking about anything other than primitive types was beyond-frustrating for me. In fact, I ended up serializing some objects as JSON so the two worlds could communicate.
It does seem insane, but I reinvented the wheel.
I followed this MSDN article to make my .NET code callable by VBA.
I used this Code Project article (I'm sure you've seen) to convert to Recordset*.
I let the frameworks handle string, integers, etc.
For all other data types I used Json.Net and a custom VBA class to do JSON serialization.
*Converted article to VB.Net and added some extra error handling.
Okay, this probably isn't the most elegant (or complete, at this point) solution; but I think it's the route I am going to go.
Instead of converting the whole thing to an ADODB Recordset (and duplicating any iterations), I just threw out the DataTable class entirely and wrote my own Recordset class as a COM Wrapper for the a generic Data Reader (via the IDataReader interface) and added a new Field class to manage the type conversion and set up Fields as an array of Field (since interop hates generics)
It basically creates a forward-only ADODB Recordset (same limitations) but has the benefit of only loading one row at a time, so the bulk of the data can be handled as managed code until you know what they want to do with it (I'm going to add methods for ToArray, ToAccessDB, ToFile, etc that use the reader) while still allowing the ability to iterate through the Recordset from excel/access/vbscript/vb6 (if that's really what they want to do.. mostly needed that for legacy support anyway)
Here is an example, in case anyone else has to do this again; somewhat modified for brevity:
Public Interface IRecordset
ReadOnly Property CursorPosition As Integer
ReadOnly Property FieldCount As Integer
ReadOnly Property Fields As Field()
Function ReadNext() As Boolean
Sub Close()
End Interface
<System.ComponentModel.DesignerCategory("")> _
<ClassInterface(ClassInterfaceType.None)> _
<ComDefaultInterface(GetType(IRecordset))> _
<Guid("E7AFBBB6-CB20-44EC-9CD2-BC70B94CD8B7")> _
Public Class Recordset : Implements IRecordset : Implements IDisposable
Private _Reader = Nothing
Private _FieldCount As Integer = Nothing
Private _Fields() As Field
Public ReadOnly Property CursorPosition As Integer Implements IRecordset.CursorPosition...
Public ReadOnly Property FieldCount As Integer Implements IRecordset.FieldCount...
Public ReadOnly Property Fields As Field() Implements IRecordset.Fields...
Friend Sub Load(ByVal reader As IDataReader)
_Reader = reader
_FieldCount = _Reader.FieldCount
_Fields = Array.CreateInstance(GetType(DataColumn), _FieldCount)
For i = 0 To _FieldCount - 1
_Fields(i) = New Field(i, Me)
Next
End Sub
'This logic kinda sucks and is dumb.
Public Function ReadNext() As Boolean Implements IRecordset.ReadNext
_EOF = Not _Reader.Read()
If _EOF Then Return False
_CursorPosition += 1
For i = 0 To _FieldCount - 1
_Fields(i)._Value = _Reader.GetValue(i).ToString
Next
Return True
End Function
From here you just need to define some type like Field or Column and add an interop wrapper for that type:
Public Interface IField
ReadOnly Property Name As String
ReadOnly Property Type As String
ReadOnly Property Value As Object
End Interface
<System.ComponentModel.DesignerCategory("")> _
<ClassInterface(ClassInterfaceType.None)> _
<Guid("6230C670-ED0A-48D2-9429-84820DC2BE6C")> _
<ComDefaultInterface(GetType(IField))> _
Public Class Field : Implements IField
Private Reader As IDataReader = Nothing
Private Index As Integer = Nothing
Public ReadOnly Property Name As String Implements IField.Name
Get
Return Reader.GetName(Index)
End Get
End Property
Public ReadOnly Property Value As Object Implements IField.Value
Get
Return Reader.GetValue(Index)
End Get
End Property
Public ReadOnly Property Type As String Implements IField.Type
Get
Return Reader.GetDataTypeName(Index).ToString
End Get
End Property
Sub New(ByVal i As Integer, ByRef r As IDataReader)
Reader = r
Index = i
End Sub
End Class
All of this is rather silly, but it seems to work well.
Note: I've only been using .Net for about 4 days now, so this might be terrible, please feel free to comment on anything extremely stupid I might be doing.

Does instantiating and initializing work only when tied to button click?

Beginner question. How come I can do this:
Public Class Form1
Private StudentsInMyRoom As New ArrayList
Public Class student
Public name As String
Public courses As ArrayList
End Class
Private Sub btnCreateStudent_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCreateStudent.Click
Dim objStudent As New student
objStudent.name = "Ivan"
objStudent.courses = New ArrayList
StudentsInMyRoom.Add(objStudent)
End Sub
End Class
But I CANNOT do this:
Public Class Form1
Private StudentsInMyRoom As New ArrayList
Public Class student
Public name As String
Public courses As ArrayList
End Class
Dim objStudent As New student
objStudent.name = "Ivan"
objStudent.courses = New ArrayList
StudentsInMyRoom.Add(objStudent)
End Class
In the second example, all of the objStudent.etc get squiggly underlined and it says "declaration expected" when I hover over it. It's the same code except now it is not tied to clicking a button. Can't figure out what is the difference.
It's because the implementation needs to be in a method, the way you have it means the code couldn't possibly be executed, how would you reference this code from elsewhere?
It doesn't have to be tied to a click however:
Private Sub AnyNameYouLike
Dim objStudent As New student
objStudent.name = "Ivan"
objStudent.courses = New ArrayList
StudentsInMyRoom.Add(objStudent)
End Sub
Will work.
Rather than tell you how to fix this code directly, I'm going to explain what I think is going wrong with your thought process, so you can also do a better job writing code in the future.
What I see here is a simple misunderstanding for someone new to programming of how classes work. When you build and define a class, you are not (yet) allocating any memory in the computer, and you are not yet telling the computer to do anything. All you are doing is telling the computer about how an object might look at some point in the future. It's not until you actually create an instance of that class that anything happens:
Public Class MyClass
Public MyField As String
End Class
'Nothing has happened yet
Public myInstance As New MyClass()
'Now finally we have something we can work with,
' but we still haven't done anything
myInstance.MyField = "Hello World"
'It's only after this last line that we put a string into memory
Classes can only hold a few specific kinds of things: Fields, Properties, Delegates (events), and Methods (Subs and Functions). All of these things in the class are declarations of something, rather than the thing itself.
Looking at your samples, the code from your second example belongs inside of a method.
If you want this code to run every time you work with a new instance of your class, then there is a special method, called a constructor, that you can use. That is declared like this:
Public Class MyClass
Public MyField As String
'This is a constructor
Public Sub New()
MyField = "Hello World"
End Sub
End Class
However, even after this last example you still haven't told the computer to do any work. Again, you must create an instance of the class before the code in that constructor will run.
This is true of all code in all .Net programs anywhere. The way your program starts out is that the .Net framework creates an instance of a special object or form and then calls (runs) a specific method in that form to sort of get the ball rolling for your program. Everything else comes from there.
Eventually you will also learn about Shared items and Modules, that can (sort of) break this rule, in that you don't have to create an instance of the object before using it. But until you are comfortable working with instances, you should not worry about that too much.
Finally, I want to point out two practices in your code that professional programmers would consider poor practice. The first is ArrayLists. I can forgive this, because I suspect you are following a course of study that just hasn't covered generics yet. I only bring it up so you can know not to get too attached to them: there is something better coming. The second is your "obj" prefix. This was considered good practice once upon a time, but is no longer fashionable and now thought to be harmful to the readability of your code. You should not use these prefixes.