Interfaces
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402) and was checked on 2026-10-19. Every example is
a complete console program that starts with Option Strict On, was built with warnings treated as errors and was run
in the .NET 10 SDK container (Linux); the output shown is real output. The examples that are meant to fail say so, and
show the compiler messages that the SDK printed. The examples use the root namespace ShelfDocs, which is why a few
runtime messages show it. The project-level imports that the SDK adds (System, System.Collections.Generic,
System.Linq and others) are why Console, List(Of T) and Enumerable methods need no Imports.
An interface is a contract: a named list of members (methods, properties, events) that a class or structure promises to
provide, with no implementation and no state. Code that depends on the interface does not know, or care, which type is
behind it. That makes interfaces the unit of substitution in Visual Basic: a unit test replaces a database store with an
in-memory fake, the For Each statement walks any IEnumerable(Of T), List(Of T).Sort orders any type that implements
IComparable(Of T), and a WPF or Windows Forms binding listens to anything that implements INotifyPropertyChanged.
The running example is the shelf library catalogue, whose Catalog class talks to an ICatalogStore and never to a
concrete store.
Visual Basic interfaces are the .NET interfaces, so what you know from C# carries over. The differences are in the syntax
and they are worth learning precisely, because Visual Basic implements an interface explicitly by name, member by
member, with the Implements clause. There is no implicit matching by name and signature as in C#, and no separate
"explicit interface implementation" form: every implementation is explicit in that it names the member it
implements, and a Private method can implement a member the way a C# explicit implementation does. The topics of this
page are collected in the table below.
| Topic | Visual Basic | Where it is explained |
|---|---|---|
Declare a contract |
|
|
Promise a contract |
|
|
Say which member implements which |
|
|
Combine contracts |
|
|
Inheritance and interfaces together |
re-implementation, |
|
Interfaces and value types |
boxing |
|
Type parameters |
|
|
The framework’s contracts |
|
|
Events and indexers in a contract |
|
|
C# features VB can only consume |
default interface members, |
Declaring an interface
[attributes] [accessmodifier] [Shadows] Interface Name [(Of typeparameters)]
[Inherits InterfaceA, InterfaceB]
member declarations
End Interface
Source: Interface Statement
-
An interface can be declared at namespace level, or inside a class, structure, module or another interface. Without an access modifier a top-level interface is
Friend(visible inside the assembly), so a contract that other assemblies must see is writtenPublic Interface. -
The members are
Sub,Function,Property,Eventand nested type declarations. A nestedEnumand a nestedClasscompiled on 17.13 and are used asIShelf.Zone; use nested types sparingly, because they hide in the contract. Fields, constants, constructors and members with a body are not allowed in an interface. The fields and constants are rejected with BC30602, a body with BC30603 and aSub Newwith BC30363 (see Errors the compiler reports). -
Interface members are implicitly
Public, and you must not write a modifier on them.Public,Shared,OverridableandMustOverrideare each rejected with BC30270: the interface says what, the implementing type says how. -
A property is declared with its shape only:
Property Name As String(read/write),ReadOnly Property Name As StringorWriteOnly Property Name As String. There is noGet/Setblock. The implementing property may offer more than the interface asks for: aReadOnlyinterface property can be implemented by a read/write property (the extra setter is just not reachable through the interface). -
A parameter in an interface member is written exactly as in a class:
ByVal/ByRef,Optionalwith a default,ParamArray. The implementation must have the same parameter types, modifiers (ByValversusByRef), return type and, for anOptionalparameter, the same default value: an implementation that wroteOptional text As String = "b"for an interface member that says= "a", or that droppedOptional, is BC30401. The parameter names may differ (countin the interface,nin the class). -
By convention the name starts with
I(ICatalogStore,IComparable); the .NET design guidelines use that prefix for every interface, and Visual Basic code follows it.
A new interface with a few members, two implementations and a client:
Option Strict On
Public MustInherit Class CatalogItem
Public Property Id As Integer
Public Property Title As String = ""
Public MustOverride ReadOnly Property Kind As String
End Class
Public Class Book
Inherits CatalogItem
Public Overrides ReadOnly Property Kind As String
Get
Return "Book"
End Get
End Property
End Class
Public Class Magazine
Inherits CatalogItem
Public Overrides ReadOnly Property Kind As String
Get
Return "Magazine"
End Get
End Property
End Class
Public Interface ICatalogStore
Function GetAll() As IReadOnlyList(Of CatalogItem)
Sub Add(item As CatalogItem)
Function Find(id As Integer) As CatalogItem
End Interface
Public Class InMemoryCatalogStore
Implements ICatalogStore
Private ReadOnly _items As New List(Of CatalogItem)
Public Function GetAll() As IReadOnlyList(Of CatalogItem) Implements ICatalogStore.GetAll
Return _items
End Function
Public Sub Add(item As CatalogItem) Implements ICatalogStore.Add
_items.Add(item)
End Sub
Public Function Find(id As Integer) As CatalogItem Implements ICatalogStore.Find
Return _items.FirstOrDefault(Function(item) item.Id = id)
End Function
End Class
' A decorator: it is an ICatalogStore and it has one.
Public Class CountingCatalogStore
Implements ICatalogStore
Private ReadOnly _inner As ICatalogStore
Public ReadOnly Property Calls As Integer
Public Sub New(inner As ICatalogStore)
_inner = inner
End Sub
Public Function GetAll() As IReadOnlyList(Of CatalogItem) Implements ICatalogStore.GetAll
_Calls += 1
Return _inner.GetAll()
End Function
Public Sub Add(item As CatalogItem) Implements ICatalogStore.Add
_Calls += 1
_inner.Add(item)
End Sub
Public Function Find(id As Integer) As CatalogItem Implements ICatalogStore.Find
_Calls += 1
Return _inner.Find(id)
End Function
End Class
Public Class Catalog
Private ReadOnly _store As ICatalogStore
Public Sub New(store As ICatalogStore)
_store = store
End Sub
Public Sub Add(item As CatalogItem)
_store.Add(item)
End Sub
Public Function Count() As Integer
Return _store.GetAll().Count
End Function
End Class
Module Program
Sub Main()
Dim counting As New CountingCatalogStore(New InMemoryCatalogStore())
Dim catalog As New Catalog(counting)
catalog.Add(New Book With {.Id = 1, .Title = "Domain Modelling"})
catalog.Add(New Magazine With {.Id = 2, .Title = "Shelf Monthly"})
Console.WriteLine("items: " & catalog.Count())
Console.WriteLine("calls on the decorator: " & counting.Calls)
Dim store As ICatalogStore = counting
Console.WriteLine("Find(2): " & store.Find(2).Title)
Console.WriteLine("Find(9) is Nothing: " & (store.Find(9) Is Nothing))
Console.WriteLine("calls now: " & counting.Calls)
' An interface variable lets the compiler check what may be called, nothing more.
Console.WriteLine("variable type: " & store.GetType().Name)
Console.WriteLine("is ICatalogStore: " & (TypeOf counting Is ICatalogStore))
Dim asDisposable = TryCast(store, IDisposable)
Console.WriteLine("is IDisposable: " & (asDisposable IsNot Nothing))
End Sub
End Module
items: 2
calls on the decorator: 3
Find(2): Shelf Monthly
Find(9) is Nothing: True
calls now: 5
variable type: CountingCatalogStore
is ICatalogStore: True
is IDisposable: False
Source: Interfaces (Visual Basic)
What the example shows:
-
Catalogdepends onICatalogStore. Constructing it with aCountingCatalogStorethat wraps anInMemoryCatalogStorerequired no change toCatalog. That is the decorator pattern, and it is possible because the decorator is also anICatalogStore(see Design Patterns in Visual Basic). -
A variable declared
As ICatalogStoreexposes only the interface’s members plus the members ofObject. The run-time type (store.GetType().Name) is stillCountingCatalogStore, andTypeOf ... Is ICatalogStoreandTryCast(store, IDisposable)test and convert at run time.TryCastreturnsNothingwhen the object does not implement the interface;DirectCastthrowsInvalidCastException. -
Find(9) Is NothingisTrue: the contract ofFindis "returnsNothingwhen there is no such item". An interface cannot express that rule in the language, so write it in the XML documentation of the member (see XML Documentation, Analyzers and Code Style). -
_Calls += 1increments the hidden backing field of the auto-propertyCalls. The field is named_Calls, with a leading underscore and the property’s spelling; it is accessible inside the class only. AReadOnlyauto-property needs noSet, and code inside the class can still assign to the backing field.
The structure of the example is shown below. The dashed arrows with a hollow head mean "implements".
Implementing an interface
A class or structure promises a contract with the Implements statement, which is placed first in the type, after
Inherits (if there is one) and before any member declaration. A type can name several interfaces, separated by commas
or on several Implements lines.
Public Class Report
Inherits ReportBase ' at most one base class, first
Implements INamed, IExporter ' any number of interfaces
Implements IPrinter ' more can follow on another line
...
End Class
The Implements statement alone is not enough. The compiler then demands that the type contains, for every member of
every interface named, a member that says it implements that interface member.
Program.vb(9,16): error BC30149: Class 'MemoryStore' must implement 'Function Count() As Integer' for interface 'ICatalogStore'.
Option Strict On
Public Interface ICatalogStore
Sub Add(title As String)
Function Count() As Integer
End Interface
Public Class MemoryStore
Implements ICatalogStore
Public Sub Add(title As String) Implements ICatalogStore.Add
End Sub
End Class
Module Program
Sub Main()
End Sub
End Module
Source: Implements Statement
The message names the missing member with its full signature, which is exactly what you need to paste into the class.
Visual Studio offers "Implement Interface" on the light bulb of the Implements line (or on the error) and writes
every missing member with the clauses filled in.
The Implements clause
The member that implements an interface member says so with an Implements clause after its signature:
Public Sub Save() Implements IExporter.Export
Public Function Compare(x As Book, y As Book) As Integer Implements IComparer(Of Book).Compare
Public Property Name As String Implements INamed.Name
Public Event Changed As EventHandler Implements IShelfLabels.Changed
Source: Implements Clause
The rules, all of which the examples below demonstrate:
-
The member kind must match (
Subfor aSub,Functionfor aFunction,Propertyfor aProperty,Eventfor anEvent) and so must the parameters and the return type. A mismatch is BC30401. -
The member name does not have to match.
Save()above implementsExport(). -
The accessibility does not matter to the compiler. A
Public,FriendorPrivatemember can implement an interface member. If it isPrivate, nobody can call it through the class, but a variable of the interface type reaches it. This is the Visual Basic counterpart of a C# explicit interface implementation. -
One member can implement several interface members:
Implements IPrinter.Print, IArchiver.Archive. The signatures of all of them must be compatible with the member. -
One interface member can be implemented by only one member of a type. A second member that names the same interface member is BC30583.
-
A member that implements nothing is an ordinary member of the class.
-
For a member inherited from a base interface, the clause may name the interface that declares it (
Implements IReadable.Read) or the derived interface that the type implements (Implements IStore.Read); both compile and map the same member. -
Members that implement an interface member cannot be
Shared(BC30505), and aModulecannot implement an interface at all (BC30231 and BC31083); an interface is implemented by instances. -
A
ReadOnlyinterface property can be implemented by a read/write property. The reverse does not compile: a read/write interface property implemented by aReadOnlyproperty is BC31444.
Option Strict On
Public Interface INamed
Property Name As String
End Interface
Public Interface IExporter
Sub Export()
ReadOnly Property Format As String
End Interface
Public Interface IPrinter
Sub Print()
End Interface
Public Interface IArchiver
Sub Archive()
End Interface
Public Interface IAuditable
Sub Audit()
End Interface
Public Class Report
Implements INamed, IExporter, IPrinter, IArchiver, IAuditable
' An auto-property can implement an interface property.
Public Property Name As String = "Quarterly loans" Implements INamed.Name
' A ReadOnly interface property can be implemented by a read/write property.
Public Property Format As String = "csv" Implements IExporter.Format
' The method name does not have to match the interface member name.
Public Sub Save() Implements IExporter.Export
Console.WriteLine("exporting '" & Name & "' as " & Format)
End Sub
' One method can implement members of several interfaces.
Public Sub Output() Implements IPrinter.Print, IArchiver.Archive
Console.WriteLine("Output called")
End Sub
' A Private method can implement a member: only the interface can reach it.
Private Sub AuditEntry() Implements IAuditable.Audit
Console.WriteLine("audited " & Name)
End Sub
End Class
Module Program
Sub Main()
Dim report As New Report()
Dim exporter As IExporter = report
Dim printer As IPrinter = report
Dim archiver As IArchiver = report
Dim named As INamed = report
Dim auditable As IAuditable = report
named.Name = "Overdue loans"
exporter.Export()
printer.Print()
archiver.Archive()
auditable.Audit()
report.Save()
report.Output()
Console.WriteLine(named.Name & " / " & exporter.Format)
End Sub
End Module
exporting 'Overdue loans' as csv
Output called
Output called
audited Overdue loans
exporting 'Overdue loans' as csv
Output called
Overdue loans / csv
Source: Implements Clause
Read the output in three parts. The first four lines are the calls made through the interface variables
(exporter.Export() reaches Save, printer.Print() and archiver.Archive() both reach Output,
auditable.Audit() reaches the Private AuditEntry; named.Name = ... wrote through INamed, which is why
Save prints the new name). Lines five and six are the calls made on the class variable, report.Save() and
report.Output(). The last line reads the property through the two interfaces. AuditEntry cannot be called through
report: the compiler reports BC30390 (the member is Private), and report.Audit() is BC30456 because the class has
no member of that name.
A mapping you can inspect
The mapping from the interface member to the implementing member is recorded in the compiled type and is available to
reflection through Type.GetInterfaceMap. A program that prints the map for the Private AuditEntry above printed:
interface: Audit
target : AuditEntry private=True virtual=True final=True
The implementing method keeps its own name (AuditEntry), unlike a C# explicit implementation, whose method name
includes the interface name. It is Private, virtual and final (sealed): not reachable by name, still dispatched
through the interface, and not overridable by a derived class unless that class re-implements the interface (see
Dispatch and re-implementation).
Name clashes and several interfaces
Two interfaces can declare the same member name with the same signature. With C#, that is the textbook reason to use an explicit implementation. With Visual Basic the clause already names the interface, so the clash resolves itself: give each interface member its own implementing method, or let one method serve both.
Option Strict On
Public Interface ILender
Sub [Return]()
End Interface
Public Interface IReturnable
Sub [Return]()
End Interface
' Two interfaces declare a member with the same signature. The class
' picks, member by member, what each interface member runs.
Public Class Loan
Implements ILender, IReturnable
Public Sub LenderReturn() Implements ILender.Return
Console.WriteLine("lender side: book is back on the shelf")
End Sub
Public Sub BorrowerReturn() Implements IReturnable.Return
Console.WriteLine("borrower side: receipt printed")
End Sub
' Not an implementation of anything: it is just a method called Return.
Public Sub [Return]()
Console.WriteLine("Loan.Return called through the class")
End Sub
End Class
Public Class SimpleLoan
Implements ILender, IReturnable
' One method can serve both members when the behaviour is the same.
Public Sub [Return]() Implements ILender.Return, IReturnable.Return
Console.WriteLine("SimpleLoan.Return")
End Sub
End Class
Module Program
Sub Main()
Dim loan As New Loan()
DirectCast(loan, ILender).Return()
DirectCast(loan, IReturnable).Return()
loan.Return()
Dim simple As New SimpleLoan()
Dim lender As ILender = simple
Dim returnable As IReturnable = simple
lender.Return()
returnable.Return()
End Sub
End Module
lender side: book is back on the shelf
borrower side: receipt printed
Loan.Return called through the class
SimpleLoan.Return
SimpleLoan.Return
Source: Implements Clause
Three things are visible here. Return is a reserved word, so a member with that name is written [Return] in the
declaration (and Return after a dot, where the context makes it a name). Loan has three different methods, and which
one runs depends on the type of the expression, not on the object. And SimpleLoan shows the one-method-for-two-members
form, which is correct when the two interfaces mean the same thing by the member.
Interface inheritance and name clashes
An interface can inherit one or more interfaces with the Inherits statement inside the interface body. (A class has at
most one base class, an interface has any number of base interfaces; that is the one place where Visual Basic has
multiple inheritance.) A type that implements the derived interface implements every base interface too, and the
Implements clause of a member that comes from a base interface names the interface that declares it.
Option Strict On
Public Interface IReadable
Function Read(id As Integer) As String
End Interface
Public Interface IWritable
Sub Write(id As Integer, text As String)
End Interface
' An interface can inherit several interfaces. Implementing it means implementing all members.
Public Interface IReadWriteStore
Inherits IReadable, IWritable
ReadOnly Property Count As Integer
End Interface
Public Class DictionaryStore
Implements IReadWriteStore
Private ReadOnly _data As New Dictionary(Of Integer, String)
Public ReadOnly Property Count As Integer Implements IReadWriteStore.Count
Get
Return _data.Count
End Get
End Property
' The inherited members are named after the interface that declares them.
Public Function Read(id As Integer) As String Implements IReadable.Read
Dim text As String = Nothing
Return If(_data.TryGetValue(id, text), text, "(missing)")
End Function
Public Sub Write(id As Integer, text As String) Implements IWritable.Write
_data(id) = text
End Sub
End Class
Module Program
Sub Show(source As IReadable, id As Integer)
Console.WriteLine(id & " -> " & source.Read(id))
End Sub
Sub Fill(target As IWritable)
target.Write(1, "Domain Modelling")
target.Write(2, "Shelf Monthly")
End Sub
Sub Main()
Dim store As IReadWriteStore = New DictionaryStore()
Fill(store) ' IReadWriteStore widens to IWritable
Show(store, 1) ' ... and to IReadable
Show(store, 3)
Console.WriteLine("count: " & store.Count)
' The inherited members are available through the derived interface.
Console.WriteLine(store.Read(2))
' Narrowing needs a cast, and the cast can fail.
Dim readOnlyView As IReadable = store
Dim writable = TryCast(readOnlyView, IWritable)
Console.WriteLine("back to IWritable: " & (writable IsNot Nothing))
End Sub
End Module
1 -> Domain Modelling
3 -> (missing)
count: 2
Shelf Monthly
back to IWritable: True
Source: Inherits Statement
-
A derived interface converts implicitly to each of its base interfaces (
IReadWriteStoretoIReadableandIWritable). The opposite direction needs a cast:TryCast(readOnlyView, IWritable)returnsNothingwhen the object does not implement it. -
Inheritsmust be the first statement in the body. A type that implements the derived interface does not have to repeat the base interfaces in its ownImplementsstatement. -
An interface cannot inherit from itself, directly or indirectly (the compiler reports a cycle).
When two base interfaces declare a member with the same name, a call through the derived interface is ambiguous. The
compiler reports BC30521 on the call and the program does not build. The remedies are a cast to the interface that
declares the member you mean, or Shadows on a new member in the derived interface that hides both:
Option Strict On
Public Interface IReadable
ReadOnly Property Name As String
End Interface
Public Interface IWritable
ReadOnly Property Name As String
End Interface
' Inheriting two interfaces with a member of the same name makes a call on the derived interface ambiguous.
Public Interface IReadWrite
Inherits IReadable, IWritable
End Interface
' Shadows declares a new member that hides both inherited ones.
Public Interface IShadowedReadWrite
Inherits IReadable, IWritable
Shadows ReadOnly Property Name As String
End Interface
Public Class Store
Implements IReadWrite, IShadowedReadWrite
Private ReadOnly Property ReadName As String Implements IReadable.Name
Get
Return "read side"
End Get
End Property
Private ReadOnly Property WriteName As String Implements IWritable.Name
Get
Return "write side"
End Get
End Property
Private ReadOnly Property BothName As String Implements IShadowedReadWrite.Name
Get
Return "shadowing member"
End Get
End Property
End Class
Module Program
Sub Main()
Dim store As New Store()
Dim ambiguous As IReadWrite = store
' ambiguous.Name is BC30521. Convert to the interface that declares the member you mean.
Console.WriteLine(DirectCast(ambiguous, IReadable).Name)
Console.WriteLine(DirectCast(ambiguous, IWritable).Name)
Dim shadowed As IShadowedReadWrite = store
Console.WriteLine(shadowed.Name)
Console.WriteLine(DirectCast(shadowed, IReadable).Name)
End Sub
End Module
read side
write side
shadowing member
read side
Source: Shadows
The same program with Dim ambiguous As IReadWrite = store followed by ambiguous.Name was compiled for this page:
Program.vb(17,33): error BC30521: Overload resolution failed because no accessible 'Name' is most specific for these arguments:
The error text continues with the two candidate declarations, one per line. The first line is enough to recognise it.
Dispatch and re-implementation
An interface call is virtual: the runtime finds the method that the object’s type mapped to the interface member. If
the implementing member is Overridable, a derived class can override it and the interface call reaches the override,
exactly as with an ordinary virtual method. But a derived class can also hide the implementing member with Shadows, and
then two different things can happen, depending on whether the derived class re-implements the interface by repeating
Implements.
Option Strict On
Public Interface IDescribable
Function Describe() As String
End Interface
Public Class CatalogItem
Implements IDescribable
' Overridable: the interface call is dispatched to the most derived override.
Public Overridable Function Describe() As String Implements IDescribable.Describe
Return "CatalogItem.Describe"
End Function
End Class
Public Class Book
Inherits CatalogItem
Public Overrides Function Describe() As String
Return "Book.Describe"
End Function
End Class
Public Class ShadowingItem
Inherits CatalogItem
' Shadows hides the base method for class callers, but the base class still implements the interface.
Public Shadows Function Describe() As String
Return "ShadowingItem.Describe (shadows)"
End Function
End Class
Public Class Reimplemented
Inherits CatalogItem
Implements IDescribable ' re-implementation: the interface is mapped again
Public Shadows Function Describe() As String Implements IDescribable.Describe
Return "Reimplemented.Describe"
End Function
End Class
Module Program
Sub Show(label As String, item As CatalogItem)
Dim viaClass = item.Describe()
Dim viaInterface = DirectCast(item, IDescribable).Describe()
Console.WriteLine(label.PadRight(14) & viaClass.PadRight(28) & viaInterface)
End Sub
Sub Main()
Console.WriteLine("variable".PadRight(14) & "via class".PadRight(28) & "via interface")
Show("Book", New Book())
Show("ShadowingItem", New ShadowingItem())
Show("Reimplemented", New Reimplemented())
' With the exact derived type the shadowing methods are the ones seen.
Dim shadowing As New ShadowingItem()
Dim reimplemented As New Reimplemented()
Console.WriteLine(shadowing.Describe())
Console.WriteLine(reimplemented.Describe())
End Sub
End Module
variable via class via interface
Book Book.Describe Book.Describe
ShadowingItem CatalogItem.Describe CatalogItem.Describe
Reimplemented CatalogItem.Describe Reimplemented.Describe
ShadowingItem.Describe (shadows)
Reimplemented.Describe
Source: Implements Statement
The table summarises the three rows of output.
| Derived class | Call through the class | Call through the interface |
|---|---|---|
|
its override |
its override: the interface method is the virtual method |
|
the base method; |
the base method: the interface is still mapped to |
|
the base method through a |
the new method: the derived class has taken over the interface |
The last two lines of the output show what a caller holding the exact derived type sees: the Shadows function in both
cases, because the compiler binds shadowing.Describe() to the most derived declaration visible by name. Prefer
Overridable/Overrides and keep the interface mapped in one place. Re-implementation is legitimate when you do not
own the base class, but it makes the behaviour depend on the static type of the reference, which is exactly what a
virtual method is meant to avoid. See
Inheritance and Polymorphism for Shadows
versus Overrides.
Structures that implement interfaces
A structure can implement interfaces (Isbn of the shelf implements IEquatable(Of Isbn)). Converting a structure to an
interface type boxes it: the common language runtime copies the value into a new object on the heap and the interface
variable refers to that copy. Calls through the interface mutate the copy.
Option Strict On
Public Interface ICounter
Sub Increment()
ReadOnly Property Value As Integer
End Interface
Public Structure Counter
Implements ICounter
Private _value As Integer
Public Sub Increment() Implements ICounter.Increment
_value += 1
End Sub
Public ReadOnly Property Value As Integer Implements ICounter.Value
Get
Return _value
End Get
End Property
End Structure
Module Program
Sub Main()
Dim a As New Counter()
a.Increment()
Console.WriteLine("a.Value after a.Increment(): " & a.Value)
' Converting to the interface boxes a copy of the structure.
Dim boxed As ICounter = a
boxed.Increment()
boxed.Increment()
Console.WriteLine("boxed.Value after two calls: " & boxed.Value)
Console.WriteLine("a.Value is unchanged: " & a.Value)
' Two boxes are two copies; assigning a box copies the reference, not the structure.
Dim alias1 As ICounter = boxed
alias1.Increment()
Console.WriteLine("boxed.Value through the alias: " & boxed.Value)
' A constrained generic call does not box.
Dim b As New Counter()
Bump(b)
Console.WriteLine("b.Value after Bump(Of T) with ByRef: " & b.Value)
End Sub
Sub Bump(Of T As {Structure, ICounter})(ByRef counter As T)
counter.Increment()
End Sub
End Module
a.Value after a.Increment(): 1
boxed.Value after two calls: 3
a.Value is unchanged: 1
boxed.Value through the alias: 4
b.Value after Bump(Of T) with ByRef: 1
Source: Structure Statement
Read the output line by line. a is incremented once, so a.Value is 1. boxed = a copies a (value 1) into a
box; two increments through the box make it 3, while a stays 1. Assigning boxed to alias1 copies the
reference to the same box, so the increment through alias1 shows through boxed (4). The generic method Bump
constrained with As {Structure, ICounter} takes the argument ByRef and calls the interface member on the original
structure without boxing it, which is the way to keep a mutable structure mutable behind an interface. Mutable
structures remain a design smell; the better answer is an immutable structure with methods that return a new value (see
Structures and Value Types).
Two practical consequences:
-
A generic algorithm over
T As IComparable(Of T)does not box whenTis a structure, because the JIT compiles a separate version for each structure type and calls the interface method directly. The same algorithm over a variable declaredAs IComparable(Of T)boxes every element. -
A
For Eachover aList(Of T)uses the structure enumeratorList(Of T).Enumerator, notIEnumerator(Of T), and does not box; through anIEnumerable(Of T)variable the same loop does.
Generic interfaces and variance
An interface can have type parameters with constraints, exactly like a class (see
Generics). The constraint T As CatalogItem of IRepository is
checked wherever the interface is closed: IRepository(Of String) does not compile. A type can implement the same generic
interface several times with different type arguments, as long as each closed interface is distinct.
An interface type parameter may be marked Out (covariant: it appears only in return positions) or In (contravariant:
it appears only in parameters). Variance is allowed for interfaces and delegates, never for classes or structures. The
compiler checks the declaration and, under Option Strict On, converts between closed types that differ by variance
without a cast.
Option Strict On
Public MustInherit Class CatalogItem
Public Property Title As String = ""
End Class
Public Class Book
Inherits CatalogItem
End Class
Public Class Magazine
Inherits CatalogItem
End Class
' T must be a CatalogItem: the constraint is checked wherever the interface is closed.
Public Interface IRepository(Of T As CatalogItem)
Sub Add(item As T)
Function GetAll() As IEnumerable(Of T)
End Interface
Public Class ListRepository(Of T As CatalogItem)
Implements IRepository(Of T)
Private ReadOnly _items As New List(Of T)
Public Sub Add(item As T) Implements IRepository(Of T).Add
_items.Add(item)
End Sub
Public Function GetAll() As IEnumerable(Of T) Implements IRepository(Of T).GetAll
Return _items
End Function
End Class
' Out: T appears only in output positions. In: T appears only in input positions.
Public Interface IProducer(Of Out T)
Function Produce() As T
End Interface
Public Interface IPrinter(Of In T)
Sub Print(item As T)
End Interface
Public Class BookProducer
Implements IProducer(Of Book)
Public Function Produce() As Book Implements IProducer(Of Book).Produce
Return New Book With {.Title = "Produced"}
End Function
End Class
Public Class ItemPrinter
Implements IPrinter(Of CatalogItem)
Public Sub Print(item As CatalogItem) Implements IPrinter(Of CatalogItem).Print
Console.WriteLine("printing " & item.GetType().Name & ": " & item.Title)
End Sub
End Class
Module Program
Sub Main()
Dim books As IRepository(Of Book) = New ListRepository(Of Book)()
books.Add(New Book With {.Title = "Shelf Design"})
books.Add(New Book With {.Title = "Domain Modelling"})
' IEnumerable(Of Book) is covariant: it converts to IEnumerable(Of CatalogItem).
Dim items As IEnumerable(Of CatalogItem) = books.GetAll()
Console.WriteLine("items: " & items.Count())
' IProducer(Of Book) widens to IProducer(Of CatalogItem) (Out).
Dim producer As IProducer(Of CatalogItem) = New BookProducer()
Console.WriteLine("produced: " & producer.Produce().Title)
' IPrinter(Of CatalogItem) widens to IPrinter(Of Book) (In).
Dim printer As IPrinter(Of Book) = New ItemPrinter()
printer.Print(New Book With {.Title = "Async Cataloguing"})
' IRepository(Of T) is invariant: this line would be BC36757 under Option Strict On.
' Dim wrong As IRepository(Of CatalogItem) = books
Console.WriteLine(GetType(IRepository(Of Book)).Name & " / " & GetType(IProducer(Of )).Name)
End Sub
End Module
items: 2
produced: Produced
printing Book: Async Cataloguing
IRepository`1 / IProducer`1
Three conversions are demonstrated. A GetAll() returning IEnumerable(Of Book) is assignable to an
IEnumerable(Of CatalogItem) because IEnumerable(Of Out T) is covariant. A BookProducer (an
IProducer(Of Book)) is assignable to IProducer(Of CatalogItem) because Out T allows widening. An ItemPrinter, which
prints any CatalogItem, is usable where an IPrinter(Of Book) is wanted because In T allows narrowing. The line that
the example comments out, assigning an IRepository(Of Book) to an IRepository(Of CatalogItem), is invalid because
IRepository(Of T) has T in both an input (Add) and an output (GetAll) position, so it is invariant. The compiler
reports it as BC36757 and, helpfully but misleadingly, suggests Out T:
Program.vb(17,52): error BC36757: 'IRepository(Of Book)' cannot be converted to 'IRepository(Of CatalogItem)'. Consider changing the 'T' in the definition of 'Interface IRepository(Of T)' to an Out type parameter, 'Out T'.
That program declared IRepository(Of T) without constraints and with an Add(item As T) member, which is why Out T
could not be applied to it: a covariant T may appear only in output positions. The suggestion is generic and does not
check the interface. Arrays are covariant too (a Book() converts to a CatalogItem()), but a store into such an array
can fail at run time with ArrayTypeMismatchException; the generic interfaces are checked at compile time instead.
Implementing the same generic interface twice:
Option Strict On
Public Interface IConverter(Of TIn, TOut)
Function Convert(value As TIn) As TOut
End Interface
' The same generic interface can be implemented several times with different arguments.
Public Class FormatConverter
Implements IConverter(Of Integer, String), IConverter(Of Date, String)
Public Function ConvertNumber(value As Integer) As String Implements IConverter(Of Integer, String).Convert
Return "#" & value.ToString("D4")
End Function
Public Function ConvertDate(value As Date) As String Implements IConverter(Of Date, String).Convert
Return value.ToString("yyyy-MM-dd", Globalization.CultureInfo.InvariantCulture)
End Function
End Class
Module Program
Sub Main()
Dim converter As New FormatConverter()
Dim numbers As IConverter(Of Integer, String) = converter
Dim dates As IConverter(Of Date, String) = converter
Console.WriteLine(numbers.Convert(42))
Console.WriteLine(dates.Convert(New Date(2026, 10, 19)))
Console.WriteLine(GetType(FormatConverter).GetInterfaces().Length & " interfaces")
For Each iface In GetType(FormatConverter).GetInterfaces().OrderBy(Function(t) t.ToString())
Console.WriteLine(" " & iface.ToString())
Next
End Sub
End Module
#0042
2026-10-19
2 interfaces
ShelfDocs.IConverter`2[System.DateTime,System.String]
ShelfDocs.IConverter`2[System.Int32,System.String]
Source: Implements Clause
Each member names the closed interface it implements (IConverter(Of Integer, String).Convert), and each closed
interface is its own entry in GetInterfaces(). A call converter.Convert(...) on the class variable would not
compile, since the class has no member named Convert; callers use the interface variables.
The standard .NET interfaces
The framework contracts that a Visual Basic program implements most often are listed here with the namespace that holds them. Older sources get several of these wrong, because the non-generic ones moved or were replaced by generic versions; use the generic interface whenever there is one.
| Interface | Namespace | What it is for |
|---|---|---|
|
|
natural order of a type: |
|
|
the non-generic version; implement both when you want old APIs ( |
|
|
typed equality without boxing; |
|
|
an external order, kept apart from the type: |
|
|
an external equality and hash for dictionaries and sets |
|
|
|
|
|
mutable collection contracts; prefer the read-only versions in signatures |
|
|
read-only views; the right return type of |
|
|
|
|
|
data binding in WPF, Windows Forms, MAUI |
|
|
observable collections for UI lists |
|
|
|
|
|
the |
|
|
avoid: it does not say whether the copy is shallow or deep |
|
|
service lookup behind dependency injection |
IComparable, IEquatable and IComparer
An ordering and an equality both belong with the type, and a second ordering belongs in a separate comparer.
IComparable(Of T).CompareTo returns a negative number, zero or a positive number; it must be consistent
with IEquatable(Of T).Equals (two objects that compare as zero should be equal) and with GetHashCode (equal objects
must produce equal hashes). Visual Basic overloads Equals: the typed Equals(other As Book) implements the interface
and needs Overloads, and the Overrides of Equals(Object) delegates to it
(see Operator Overloading and Equality).
Option Strict On
Public Class Book
Implements IComparable(Of Book), IEquatable(Of Book), IComparable
Public ReadOnly Property Title As String
Public ReadOnly Property Year As Integer
Public Sub New(title As String, year As Integer)
Me.Title = title
Me.Year = year
End Sub
' Natural order: by title, ignoring case.
Public Function CompareTo(other As Book) As Integer Implements IComparable(Of Book).CompareTo
If other Is Nothing Then Return 1
Return String.Compare(Title, other.Title, StringComparison.OrdinalIgnoreCase)
End Function
' The non-generic interface is still used by older APIs.
Private Function CompareToObject(obj As Object) As Integer Implements IComparable.CompareTo
Dim other = TryCast(obj, Book)
If obj IsNot Nothing AndAlso other Is Nothing Then
Throw New ArgumentException("Object must be a Book.", NameOf(obj))
End If
Return CompareTo(other)
End Function
Public Overloads Function Equals(other As Book) As Boolean Implements IEquatable(Of Book).Equals
Return other IsNot Nothing AndAlso
String.Equals(Title, other.Title, StringComparison.OrdinalIgnoreCase) AndAlso
Year = other.Year
End Function
Public Overrides Function Equals(obj As Object) As Boolean
Return Equals(TryCast(obj, Book))
End Function
Public Overrides Function GetHashCode() As Integer
Return HashCode.Combine(Title.ToLowerInvariant(), Year)
End Function
Public Overrides Function ToString() As String
Return Title & " (" & Year & ")"
End Function
End Class
Public Class ByYearThenTitle
Implements IComparer(Of Book)
Public Function Compare(x As Book, y As Book) As Integer Implements IComparer(Of Book).Compare
Dim byYear = x.Year.CompareTo(y.Year)
Return If(byYear <> 0, byYear, x.CompareTo(y))
End Function
End Class
Module Program
Sub Main()
Dim books As New List(Of Book) From {
New Book("Shelf Design", 2021),
New Book("domain modelling", 2019),
New Book("Async Cataloguing", 2021)
}
books.Sort() ' uses IComparable(Of Book)
Console.WriteLine("natural : " & String.Join("; ", books))
books.Sort(New ByYearThenTitle()) ' uses IComparer(Of Book)
Console.WriteLine("year, title: " & String.Join("; ", books))
Dim set1 As New HashSet(Of Book) From {New Book("Shelf Design", 2021)}
Console.WriteLine("contains equal book: " & set1.Contains(New Book("SHELF DESIGN", 2021)))
Console.WriteLine("Max: " & books.Max().ToString())
Dim sorted As New SortedSet(Of Book)(books)
Console.WriteLine("SortedSet first: " & sorted.Min.ToString())
Dim old As IComparable = New Book("A", 1)
Try
old.CompareTo("not a book")
Catch ex As ArgumentException
Console.WriteLine(ex.GetType().Name & ": " & ex.Message.Split(ControlChars.Lf)(0))
End Try
End Sub
End Module
natural : Async Cataloguing (2021); domain modelling (2019); Shelf Design (2021)
year, title: domain modelling (2019); Async Cataloguing (2021); Shelf Design (2021)
contains equal book: True
Max: Shelf Design (2021)
SortedSet first: Async Cataloguing (2021)
ArgumentException: Object must be a Book. (Parameter 'obj')
Source: IComparable<T> Interface
-
books.Sort()callsCompareTo;books.Sort(New ByYearThenTitle())uses theIComparer(Of Book)and leaves the type’s natural order untouched. Prefer a comparer when the order is context-specific. -
HashSet(Of Book).Contains(New Book("SHELF DESIGN", 2021))isTruebecauseEquals(Book)ignores case andGetHashCodehashes the lower-case title: the two methods agree. A hash code that included the title with its original case would make the lookup fail even thoughEqualsreturnsTrue. -
The
PrivateIComparable.CompareTo(Object)throwsArgumentExceptionfor an object of another type, which is the contract of the non-generic interface.Privateis correct for it, since it is not part of the type’s usual API. -
CompareTo(Nothing)must return a positive number: any instance is greater thanNothing. -
Max()andSortedSet(Of Book).MinuseIComparable(Of Book). Without anIComparableimplementation,Max()on aList(Of Plain)compiled and threwArgumentExceptionwith the message "At least one object must implement IComparable." at run time, because the LINQ method has no constraint that the compiler could check.
IEnumerable(Of T)
Implement IEnumerable(Of T) to make a type usable in For Each and LINQ. In Visual Basic an Iterator function
(see Iterators and Yield) writes the enumerator for you.
The generic interface inherits the non-generic IEnumerable, so both GetEnumerator methods are needed: the generic one
is the iterator, the non-generic one is a Private method that forwards to it.
Option Strict On
Imports System.Collections
Public Class Bookshelf
Implements IEnumerable(Of String)
Private ReadOnly _titles As New List(Of String)
Public Sub Add(title As String)
_titles.Add(title)
End Sub
' An Iterator function implements the generic interface in a few lines.
Public Iterator Function GetEnumerator() As IEnumerator(Of String) Implements IEnumerable(Of String).GetEnumerator
For Each title In _titles
Yield title
Next
End Function
' IEnumerable(Of T) inherits the non-generic IEnumerable, which must be implemented too.
Private Function GetEnumeratorObject() As IEnumerator Implements IEnumerable.GetEnumerator
Return GetEnumerator()
End Function
End Class
Module Program
Sub Main()
Dim shelf As New Bookshelf()
shelf.Add("Shelf Design")
shelf.Add("Domain Modelling")
shelf.Add("Async Cataloguing")
For Each title In shelf
Console.WriteLine(title)
Next
' Every LINQ operator works because of the interface.
Console.WriteLine(String.Join(" | ", From t In shelf Order By t Select t.ToUpperInvariant()))
Console.WriteLine("count: " & shelf.Count())
' An old-style loop over the non-generic interface sees Object elements.
Dim untyped As IEnumerable = shelf
For Each item As Object In untyped
Console.Write(item.GetType().Name & " ")
Next
Console.WriteLine()
End Sub
End Module
Shelf Design
Domain Modelling
Async Cataloguing
ASYNC CATALOGUING | DOMAIN MODELLING | SHELF DESIGN
count: 3
String String String
Source: IEnumerable<T> Interface
For Each title In shelf needs only a public GetEnumerator method (the compiler uses the pattern, not the interface),
but LINQ and the framework APIs ask for the interface. The non-generic loop in the last lines shows that the elements
are still String objects, just typed as Object by the older interface. Implement the interface rather than wrapping
a List(Of T) return type when the type should offer only enumeration; see also
Collections.
IDisposable
IDisposable has one method, Dispose. A Using statement calls it when the block ends, even after an exception, for any
variable whose type implements it. The pattern for a type that owns only managed resources is shown below: Dispose
is idempotent, and members check the disposed state.
Option Strict On
Public Class ReportWriter
Implements IDisposable
Private _disposed As Boolean
Private ReadOnly _name As String
Public Sub New(name As String)
_name = name
Console.WriteLine("open " & _name)
End Sub
Public Sub Write(line As String)
ObjectDisposedException.ThrowIf(_disposed, Me)
Console.WriteLine(_name & ": " & line)
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
If _disposed Then Return
_disposed = True
Console.WriteLine("close " & _name)
GC.SuppressFinalize(Me)
End Sub
End Class
Module Program
Sub Main()
Dim kept As ReportWriter
Using writer As New ReportWriter("loans.txt")
writer.Write("3 loans overdue")
kept = writer
End Using
kept.Dispose() ' calling Dispose again must be harmless
Try
kept.Write("too late")
Catch ex As ObjectDisposedException
Console.WriteLine(ex.GetType().Name & ": " & ex.ObjectName)
End Try
' Using accepts any expression whose type implements IDisposable, also an interface type.
Dim disposable As IDisposable = New ReportWriter("audit.txt")
Using disposable
Console.WriteLine("working through IDisposable")
End Using
End Sub
End Module
open loans.txt
loans.txt: 3 loans overdue
close loans.txt
ObjectDisposedException: ShelfDocs.ReportWriter
open audit.txt
working through IDisposable
close audit.txt
Source: IDisposable Interface
Using disposable (an existing variable of the interface type, without As) works too: the statement needs a
reference whose static type implements IDisposable. The full pattern for finalizers and unmanaged resources, and
IAsyncDisposable, are in
Object Lifetime and Disposal.
ObjectDisposedException.ThrowIf is the .NET 7 and later helper; the exception’s ObjectName shows the type name with the
root namespace ShelfDocs.
INotifyPropertyChanged
INotifyPropertyChanged has a single event, PropertyChanged. The implementation declares the event with its
delegate type and an Implements clause; the Event statement can name the delegate type (PropertyChangedEventHandler)
instead of a parameter list. An observer attaches to the interface event with AddHandler, which also works when the
class variable is not available. The CallerMemberName attribute (see
Attributes, Caller Info and Reflection)
supplies the property name, so a typo cannot slip into a string.
Option Strict On
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public Class BookViewModel
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private _title As String = ""
Private _isAvailable As Boolean = True
Public Property Title As String
Get
Return _title
End Get
Set(value As String)
SetField(_title, value)
End Set
End Property
Public Property IsAvailable As Boolean
Get
Return _isAvailable
End Get
Set(value As Boolean)
SetField(_isAvailable, value)
End Set
End Property
' CallerMemberName supplies "Title" or "IsAvailable" from the calling property.
Private Sub SetField(Of T)(ByRef field As T, value As T, <CallerMemberName> Optional name As String = Nothing)
If EqualityComparer(Of T).Default.Equals(field, value) Then Return
field = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
End Class
Module Program
Sub Main()
Dim vm As New BookViewModel()
Dim observed As INotifyPropertyChanged = vm
AddHandler observed.PropertyChanged,
Sub(sender, e) Console.WriteLine("changed: " & e.PropertyName)
vm.Title = "Shelf Design"
vm.Title = "Shelf Design" ' same value: no event
vm.IsAvailable = False
vm.Title = "Shelf Design, 2nd edition"
End Sub
End Module
changed: Title
changed: IsAvailable
changed: Title
Source: INotifyPropertyChanged Interface
The second assignment of the same title raised no event because SetField compares the old and the new value with
EqualityComparer(Of T).Default. A property declared Property Title As String with the auto-property syntax cannot be
used for this: change notification needs a setter body. The RaiseEvent statement of an event that implements an
interface event is the usual one, and PropertyChanged is Nothing-safe (an event without handlers does nothing).
WPF and Windows Forms bindings are in
WPF Applications and
Windows Forms Applications.
Events and default properties in an interface
An interface can declare Event members and indexers. An indexer is a property marked Default, which makes the
interface variable subscriptable with labels(1). Marking the implementing property Default as well makes the
indexer work on LabelList variables, not only on interface variables. An event is implemented with an event of the same delegate type or signature and an Implements
clause.
Option Strict On
Public Interface IShelfLabels
Event Changed As EventHandler
Default ReadOnly Property Item(index As Integer) As String
ReadOnly Property Count As Integer
Sub Add(label As String)
End Interface
Public Class LabelList
Implements IShelfLabels
Private ReadOnly _labels As New List(Of String)
Public Event Changed As EventHandler Implements IShelfLabels.Changed
Default Public ReadOnly Property Item(index As Integer) As String Implements IShelfLabels.Item
Get
Return _labels(index)
End Get
End Property
Public ReadOnly Property Count As Integer Implements IShelfLabels.Count
Get
Return _labels.Count
End Get
End Property
Public Sub Add(label As String) Implements IShelfLabels.Add
_labels.Add(label)
RaiseEvent Changed(Me, EventArgs.Empty)
End Sub
End Class
Module Program
Sub Main()
Dim labels As IShelfLabels = New LabelList()
AddHandler labels.Changed, Sub(sender, e) Console.WriteLine("labels changed")
labels.Add("Fiction")
labels.Add("Reference")
' The default property is visible through the interface variable.
Console.WriteLine(labels(1) & " of " & labels.Count)
' WithEvents also works with an interface-typed field; see events-and-handlers.
End Sub
End Module
labels changed
labels changed
Reference of 2
Source: Event Statement
A WithEvents field may have an interface type that declares events, and a Handles clause then names the field and
the interface event (a test program with Private WithEvents _source As IChanged and Handles _source.Changed built
and printed the handler’s message). Handles and custom events are covered in
Events and Handlers, and default properties in
Properties and Default Properties.
Interfaces from C#
C# has added two features to interfaces that Visual Basic only consumes: default interface
members (an interface method with a body) and static abstract members (a contract on the type itself, the basis of
generic math). Visual Basic’s strategy is to consume new .NET features, not to extend its own syntax (see
History, Versions and Language Strategy).
The results of the experiments are below; the library is a C# project with an interface that has a default method and a
second interface with a static abstract member:
namespace CsShelf;
public interface IShelfLabel
{
string Text { get; }
// Default interface method: C# 8, runtime feature.
string Describe() => "Label: " + Text;
}
public interface ILabelFactory
{
// Static abstract member: C# 11.
static abstract IShelfLabel Create(string text);
}
public sealed class SimpleLabel : IShelfLabel
{
public SimpleLabel(string text) => Text = text;
public string Text { get; }
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>ShelfDocs</RootNamespace>
<OptionStrict>On</OptionStrict>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NuGetAudit>false</NuGetAudit>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\cs\cs.csproj" />
</ItemGroup>
</Project>
The Visual Basic client of the default method:
Option Strict On
Imports CsShelf
Module Program
Sub Main()
' SimpleLabel is written in C# and does not define Describe: the interface supplies it.
Dim simple As New SimpleLabel("Maps")
Dim label As IShelfLabel = simple
Console.WriteLine(label.Text & " -> " & label.Describe())
Console.WriteLine(DirectCast(simple, IShelfLabel).Describe())
End Sub
End Module
/w/g3-interfaces-13/vb/Program.vb(7,16): error BC30149: Class 'PlainLabel' must implement 'Function Describe() As String' for interface 'IShelfLabel'. [/w/g3-interfaces-13/vb/vb.vbproj]
/w/g3-interfaces-13/vb/Program.vb(7,16): error BC37315: Class 'PlainLabel' cannot implement interface 'IShelfLabel' because it contains shared abstract or virtual 'Function Create(text As String) As IShelfLabel'. [/w/g3-interfaces-13/vb/vb.vbproj]
The build failed. Fix the build errors and run again.
The default method can be called from Visual Basic, through a variable of the interface type, on any object that implements the interface. What Visual Basic cannot do, on 17.13, is the following (each case was compiled):
| Attempt | Compiler message | Meaning |
|---|---|---|
|
BC30456: 'Describe' is not a member of 'SimpleLabel'. |
The default method belongs to the interface, not to the class. Convert to the interface type first, as |
A Visual Basic class that implements |
BC30149: Class 'PlainLabel' must implement 'Function Describe() As String' for interface 'IShelfLabel'. |
For a type written in Visual Basic the default member counts as a required one: the class must write the member itself. |
A Visual Basic class that implements an interface with a |
BC37315: Class 'Maker' cannot implement interface 'ILabelFactory' because it contains shared abstract or virtual 'Function Create(text As String) As IShelfLabel'. |
Visual Basic cannot implement static abstract members, so it cannot implement a factory interface of this kind, nor a generic-math interface such as |
A method body, or a |
BC30603: Statement cannot appear within an interface body. (the body); BC30270: 'Shared' is not valid on an interface method declaration. |
Interfaces declared in Visual Basic cannot have implementations or static members. |
The generic-math interfaces of the base library (INumber(Of T), IAdditionOperators(Of ...)) are the common case
of the third row: a Visual Basic type cannot implement them. See
Numbers and Math for the numeric types and
Interop with C# and Other .NET Languages
for what else crosses the language boundary.
Errors the compiler reports
All messages below are the compiler’s own text, printed by the .NET 10 SDK (10.0.402) for a complete program with
Option Strict On that contained only that mistake. Learn documents most of these codes on pages of the form BC30270; BC30149, BC36757 and BC37315 had no page at the time of checking.
| Code | Message (shortened where it names your types) | Cause |
|---|---|---|
BC30149 |
Class 'MemoryStore' must implement 'Function Count() As Integer' for interface 'ICatalogStore'. |
The class names the interface and does not implement every member. |
BC30270 |
'Public' is not valid on an interface method declaration. |
A modifier on an interface member: |
BC30363 |
'Sub New' cannot be declared in an interface. |
An interface has no constructors. |
BC31444 |
'Property Name As String' cannot be implemented by a ReadOnly property. |
A read/write interface property needs both accessors in the implementation. |
BC30296 |
Interface 'IA' cannot inherit from itself: |
A cycle in |
BC37315 (generic math) |
Structure 'Money' cannot implement interface 'IAdditionOperators(Of Money, Money, Money)' because it contains shared abstract or virtual 'Function op_Addition(left As Money, right As Money)'. |
A framework interface with |
BC30401 |
'Find' cannot implement 'Find' because there is no matching function on interface 'ICatalogStore'. |
The clause names an interface member that does not exist or whose signature differs (here |
BC30401 |
'Remove' cannot implement 'Remove' because there is no matching sub on interface 'ICatalogStore'. |
The member name after the dot is not in the interface. |
BC30583 |
'ICatalogStore.Add' cannot be implemented more than once. |
Two members of the type implement the same interface member. |
BC30505 |
Methods or events that implement interface members cannot be declared 'Shared'. |
|
BC30231 and BC31083 |
'Implements' not valid in Modules. / Members in a Module cannot implement interface members. |
A |
BC30602 |
Interface members must be methods, properties, events, or type definitions. |
A field ( |
BC30603 |
Statement cannot appear within an interface body. |
A method body in an interface (the |
BC31035 |
Interface 'IAuditable' is not implemented by this class. |
An |
BC30683 |
'Inherits' statement must precede all declarations in a class. |
|
BC30390 |
'Report.Private Sub Audit()' is not accessible in this context because it is 'Private'. |
Calling a |
BC30521 |
Overload resolution failed because no accessible 'Name' is most specific for these arguments: |
The member name is declared by two base interfaces of the derived interface that you call it on. |
BC36757 |
'IRepository(Of Book)' cannot be converted to 'IRepository(Of CatalogItem)'. Consider changing the 'T' in the definition of 'Interface IRepository(Of T)' to an Out type parameter, 'Out T'. |
A conversion that needs variance on an invariant interface. |
BC30456 |
'Describe' is not a member of 'SimpleLabel'. |
A C# default interface member called on the class instead of the interface. |
BC37315 |
Class 'Maker' cannot implement interface 'ILabelFactory' because it contains shared abstract or virtual 'Function Create(text As String) As IShelfLabel'. |
A C# |
Two runtime exceptions are specific to interfaces. A DirectCast or CType to an interface that the object does not
implement throws InvalidCastException, where TryCast returns Nothing. And a call through an interface variable that
holds Nothing throws NullReferenceException, as for any object reference. A COM-exposed class that does not repeat
its interface members is a separate matter (see
COM Interop and Office Automation).
Designing with interfaces
-
Interface or base class? An interface is the right tool for a role that unrelated types play (
IDisposable,ICatalogStore) or when a type needs several roles. A base class (MustInherit) is right when the types share state and implementation and form a hierarchy (CatalogItemandBook). The two combine well: an interface for clients, aMustInherithelper class that implements it for convenience, and implementations free to ignore the helper. See Inheritance and Polymorphism. -
Small interfaces.
ICatalogStorehas three members. Clients that only read should depend on a smallerIReadOnlyCatalog, so a read-only fake or a cached view can implement it. This is the interface segregation principle; when a class implements an interface of ten members to supply one, split the interface. -
Depend on the interface at the boundary. Constructor parameters, fields and return types name the interface (
ICatalogStore,IReadOnlyList(Of CatalogItem)); the one place that saysNew InMemoryCatalogStore()is the composition root. The dependency injection container ofMicrosoft.Extensions.DependencyInjectionregistersAddSingleton(Of ICatalogStore, InMemoryCatalogStore)()and then resolves aCatalog(see Design Patterns in Visual Basic). -
Test with fakes. Because
Catalogtakes anICatalogStore, a unit test passes a small class or a library-generated mock; see Testing. -
Do not change a published interface. Adding a member breaks every implementer outside your control. In C# a default interface member can soften that; Visual Basic cannot write one, so version a contract by adding a new interface that inherits the old one (
ICatalogStore2 Inherits ICatalogStore) and test withTypeOf store Is ICatalogStore2. -
Naming.
Iprefix, PascalCase, noHungarianprefixes; an interface that names a capability uses an adjective (IComparable,IDisposable), a role uses a noun (ICatalogStore). -
Trimming and Native AOT. Code that discovers implementers by reflection (
Type.GetInterfaces,Assembly.GetTypes) depends on metadata that trimming may remove; code that names the interface and its implementers statically does not. -
Not interfaces. A
Delegatefits a contract of one method better (see Delegates and Lambda Expressions), and anEnumor aMustInheritclass fits a closed set of alternatives.
Comparison with C#
| Topic | Visual Basic | C# |
|---|---|---|
Implementing a member |
|
matching by name and signature of a public member, or |
Hiding an implementation from the class |
|
explicit interface implementation |
One method for several members |
|
one public method matches both interfaces by name |
Interface members |
|
also static members, default bodies and |
Default interface members |
can be called through the interface; types must still implement them |
declared and used |
|
cannot be implemented (BC37315) |
declared and implemented |
Interface inheritance |
|
|
Variance |
|
|
Re-implementation |
repeat |
repeat the interface in the base list |
Access modifier on an interface member |
not allowed (BC30270) |
allowed in C# 8 and later for default and static members |
The corresponding C# page is Interfaces in the C# reference.
Older material you may meet
-
Visual Basic 6 and VBA have
Implementsas well, with a convention rather than a clause: the implementing procedure must be calledInterfaceName_MemberName(IShelfLabel_Text), the interface is another class module whose methods are empty, and a class can implement an interface but cannot inherit a class. In Visual Basic .NET the method can have any name and the clause says which member it implements. Ported code usually keeps the underscored names and gains anImplementsclause. See The VB Family: VB6, VBA and VBScript. -
Older .NET Framework samples use the non-generic
IComparable,IEnumerableandICollectionwithArrayListandObjectelements, andICloneablefor copies. Today, implement the generic interfaces (IComparable(Of T),IEnumerable(Of T)) and give the non-generic ones aPrivateforwarding implementation only when an old API needs it. -
Older
INotifyPropertyChangedsamples raise the event with a string literal in every setter (RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Title"))).CallerMemberNameremoves the literals, and a renamed property can no longer leave a stale name behind. -
The "interface or abstract class" advice in older material predates default interface members. Visual Basic still cannot declare them, but it can consume them (see Interfaces from C#), so a library written in C# can add a member to an interface without breaking Visual Basic callers, while a Visual Basic class that implements it must still write the member.
-
The interface rules are the same on .NET Framework 4.x and on .NET 10 for Visual Basic. Default interface members are a runtime feature of .NET Core 3.0 and later, so a .NET Framework application cannot run a library that uses them.
References
-
Interfaces (Visual Basic programming guide) and Walkthrough: Creating and Implementing Interfaces
-
IComparable<T>, IEquatable<T>, IComparer<T>, IEnumerable<T>, IDisposable and INotifyPropertyChanged
-
Update interfaces with default interface methods and What’s new in C# 11 (static abstract members)
-
Names of Classes, Structs, and Interfaces and Interface Design