To create a .NET
component in C# (or any other .NET Language), you simply declare a
class. When the class is instantiated by the CLR, the result is a
binary component. Example C-1 shows a simple class
named MyClass
that implements the
IMessage
interface and displays a message box with
the word “Hello” when the interface’s
ShowMessage( )
method is called.
Example C-1. Building a component in .NET
namespace MyNamespace { using System; using System.Windows.Forms; public interface IMessage { void ShowMessage( ); } public class MyComponent :IMessage { public MyComponent(){}//constructor ~ MyComponent(){}//destructor public void ShowMessage( ) { MessageBox.Show("Hello!","MyComponent"); } } }
The MyComponent
class in Example C-1 is defined as public
,
making it accessible to any .NET or COM client once you export the
component to COM. You can define a class constructor to do object
initialization, as in this example, but the destructor has different
semantics than the classic C++ destructor because .NET uses
nondeterministic object finalization. You can implement other methods
to do object cleanup as well. The implementation of
ShowMessage( )
uses the static Show( )
method of the MessageBox
class. Like
in C++, C# allows you to call a class (static) method without
instantiating an object first.
Example C-1 demonstrates a few additional key points regarding developing .NET components: using namespaces and interface-based programming. These points are discussed next.
The
class definition is scoped in a
namespace. Namespaces are optional, but you are
encouraged to use them. Namespaces in .NET have the same purpose they
have in C++: to scope classes so a client can use different classes
from different vendors that have the same name. For a namespace, you
typically use the product’s name, your company’s name, or
the assembly’s name. A client of the class
MyComponent
in Example C-1 must
now refer to it by qualifying it with its containing namespace:
MyNamespace.MyComponent
Alternatively, the client can say that it is using the
MyNamespace
namespace, and avoid putting the
“MyNamespace” prefix on every type contained in that
namespace:
using MyNamespace; //MyComponent is now the same as MyNamespace.MyComponent
You can also nest namespaces within one another. For example, if your
company develops more than one product, you would typically define in
the scope of the MyCompany
namespace, the nested
namespaces Product1
, Product2
,
and so on:
namespace MyCompany { namespace Product1 { //classes and type definitions public class Component1 {...} } namespace Product2 { //other classes and type definitions } }
Clients of your components must give the full qualifying namespace to access your component:
MyCompany.Product1.Component1
Or, clients can use the using
statement:
using MyCompany.Product1; //Component1 is now the same as MyCompany.Product1.Component1
The ShowMessage( )
method in Example C-1 uses the static method Show( )
of the MessageBox
class, defined in
the System.Windows.Forms
namespace. Example C-1 therefore contains the statement:
using System.Windows.Forms;
This statement is used to simplify downstream code.
One the most important principles of component-oriented development is the separation of interfaces from implementation. COM enforces this separation by having you separate the definitions of interfaces and classes. .NET does not force you to have your class methods be part of any interface, but it is imperative that you do so to allow polymorphism between different implementations of the same interface.
Example C-1 includes an interface definition as part
of the code—there is no need for a separate IDL file. The
reserved C# word interface
allows you to define a
type that is purely virtual (it has no implementation and cannot be
instantiated by clients), just like a C++ pure virtual or abstract
class. The interface methods do not have to return
HRESULT
or any other error handling type. In case
of an error, the method implementation should throw an exception.