This chapter explains the basics of creating types in C#. If you are already familiar with C#, much of this chapter will be a review for you.
After we cover class members such as fields, properties, and methods, you’ll learn about constructors, how to create and implement interfaces, and when to use structs.
The information in this chapter is basic, but vital. Like so many things, if you don’t get the fundamentals right, nothing else will work out.
Solution: Let’s begin by declaring a simple class that will hold 3D coordinate points.
The class we’ve defined is empty so far, but we’ll fill it up throughout this chapter.
The class is defined as public, which means it is visible to every other type that references its assembly. C# defines a number of accessibility modifiers, as detailed in Table 1.1.
If the class does not specify the accessibility, it defaults to internal
.
Solution: Let’s add some usefulness to the Vertex3d
class.
Some notes on the preceding code:
• The fields are all declared private
, which is good practice in general.
• The properties are declared public
, but could also be private
, protected
, or protected internal
, as desired.
• Properties can have get
, set
, or both.
• In properties, value
is the implied argument (that is, in the code).
In the following example, 13.0
would be passed to X
in the value
argument:
Vertex3d v = new Vertex3d();
v.X = 13.0;
You will often see the following pattern:
C# has a shorthand syntax for this:
You cannot have an auto-implemented property with only a get
(if there’s no backing field, how would you set it?), but you can have a private set:
public int Field { get ; private set; }
Solution: Add the modifying keyword static
, as in the following method for adding two Vertex3d
objects:
The static method is called like in this example:
Solution: Define a special method, called a constructor, with the same name as the class, with no return type. A constructor runs when a type is created—it is never called directly.
Here are two constructors for the Vertex3d
class—one taking arguments, the other performing some default initialization.
Constructors do not need to be public. For example, you could make a protected constructor that is only accessible from derived classes. You could even make a private constructor that prevents instantiation (for utility classes) or is accessible only from other methods in the same class (static factory methods, perhaps).
Solution: Static fields can be initialized in two ways. One way is with a static constructor, which is similar to a standard constructor, but with no accessibility modifier or arguments:
However, because of performance reasons, it is preferable to initialize static fields inline whenever possible, as shown here:
Solution: Use object initialization syntax, as in the following example:
const
and readonly
Solution: Both const
and readonly
are used to define data that does not change, but there are important differences: const
fields must be defined at declaration. Because of this and the fact that they cannot change value, it implies that they belong to the type as static fields. On the other hand, readonly
fields can be set at declaration or in the constructor, but nowhere else.
Solution: In C#, you are allowed to call other constructors within the same class using the this
keyword, as shown next:
Solution: Use inheritance to reuse the base class and add new functionality.
Deriving from a class gives access to a base class’s public and protected members, but not private members.
Solution: Similar to calling other constructors from the constructor of a class, you can call specific constructors of a base class. If you do not specify a constructor, the base class’s default constructor is called. If the base class’s default constructor requires arguments, you will be required to supply them.
Solution: The base class’s method or property must be declared virtual
and must be accessible from the derived class. The derived class will use the override
keyword.
Base class references can refer to instances of the base class or any class derived from it. For example, the following will print “28,” not “13.”
You can also call base class functions from a derived class via the base
keyword.
Calling Derived.DoSomething
will print out the following:
Base.DoSomething
Derived.DoSomething
virtual
Methods and PropertiesSolution: You can still override it, but with a caveat: The override
method will only be called through a reference to the derived class. To do this, use the new
keyword (in a different context than you’re probably used to).
Here is the output of this code:
Make sure you understand why the output is the way it is.
Solution: Here’s a sample interface for some kind of playable object—perhaps an audio or video file, or even a generic stream. An interface does not specify what something is, but rather some behavior.
Note that interfaces can contain methods as well as properties. (They can also contain events, which we will cover in Chapter 15, “Delegates, Events, and Anonymous Methods.”) You do not specify access with interfaces’ members because, by definition, they are all public.
Solution: To implement an interface, you need to declare each interface method and property in your class, mark them public, and provide implementations.
Visual Studio can help you out here. When you add “: IPlayable
” after the class name, it will show a Smart Tag over it. Clicking the Smart Tag will give you the option to generate empty implementations of the interface in the class.
Solution: A class can implement multiple interfaces, separated by commas:
However, you may run into instances where two (or more) interfaces define the same method. In our small example, suppose both IPlayable
and IRecordable
have a Stop()
method defined. In this case, one interface must be made explicit.
Here is how to call these two methods:
Note that we arbitrarily decided that IRecordable
’s Stop()
method needed to be explicit—you could just have easily decided to make IPlayable
’s Stop()
method the explicit one.
Unlike in C++, where structs and classes are functionally identical, in C#, there are important and fundamental differences:
• Structs are value types as opposed to reference types, meaning that they exist on the stack. They have less memory overhead and are appropriate for small data structures. They also do not have to be declared with the new
operator.
• Structs cannot be derived from. They are inherently sealed (see Chapter 2, “Creating Versatile Types”).
• Structs may not have a parameterless constructor. This already exists implicitly and initializes all fields to zeros.
• All of a struct’s fields must be initialized in every constructor.
• Structs are passed by value, just like any other value-type, in method calls. Beware of large structs.
Solution: Defining a struct is similar to a class:
Solution: The var
keyword can be used to create anonymous types that contain properties you define inline.
This program produces the following output:
See Chapter 3, “General Coding,” for more information on how to use the var
keyword.
var might look like it’s untyped, but don’t be fooled. The compiler is generating a strongly typed object for you. Examine this code sample:
For that last line, the compiler will give this error:
Cannot implicitly convert type 'AnonymousType#2' to
'AnonymousType#1'
Solution: Mark your class as abstract.
You can also mark individual methods inside a class as abstract to avoid giving them any default implementation, as shown here:
When designing class hierarchies, you often need to decide whether classes at the root of the hierarchy (the parent classes) should be abstract base classes, or whether to implement the concrete classes in terms of interfaces.
Here are some guidelines to help make this decision.
In favor of interfaces:
• Will classes need to implement multiple base classes? This isn’t possible in C#, but it is possible to implement multiple interfaces.
• Have you separated concerns to the point where you understand the difference between what your class is and what it does? Interfaces are often about what the class does, whereas a base class can anchor what it is.
• Interfaces are often independent of what a class is and can be used in many situations. They can be added onto the class without concern for what it is.
• Interfaces often allow a very loosely coupled design.
• Deriving too many things from a base class can lead to it being bloated with too much functionality.
In favor of base classes:
• Is there reasonable common functionality or data for all derived types? An abstract base class may be useful.
• Implementing the same interface over many types can lead to a lot of repetition of code, whereas an abstract base class can group common code into a single implementation.
• An abstract base class can provide a default implementation.
• Abstract base classes tend to rigidly structure code. This may be desirable in some cases.
If you find yourself trying to put too much functionality into abstract base classes, another possibility is to look into componentizing the various areas of functionality.
As you gain experience, you’ll come to realize what makes sense in different situations. Often, some combination of the two makes sense.