Properties let you expose data members as part of your public
interface and still provide the encapsulation you want in an object-oriented
environment. Client code accesses properties as though they are accessing public
variables.
The .NET Framework assumes that you'll use properties for your public data
members. In fact, the data binding code classes in the .NET Framework,
especially WPF, support properties, not public data members. Data binding ties a
property of an object to a user interface control. But that doesn't mean
properties should be used exclusively in UI logic. You should still be using
properties for other classes and structures. Properties are far easier to change
as you discover new requirements or behaviors over time. See class
Person below.
With properties, adding multithreaded support is easy. Simply enhance the
implementation of the get and set methods to provide synchronized access to the
data. See class PersonThreadSafe below.
Properties have all the language features of methods. Properties can be virtual,
abstract, and also part of an interface definition.
namespace Item1NS
{
// Properties are far easier to change as you add or improve behavior
class Person
{
private string _FirstName;
public string FirstName
{
get { return _FirstName; }
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("FirstName cannot be null or empty");
else
_FirstName = value;
}
}
}
// Adding multi-threading support to properties is also easy
class PersonThreadSafe
{
private string _FirstName;
private object _lock = new object();
public string FirstName
{
get { return _FirstName; }
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("FirstName cannot be null or empty");
else
lock (_lock)
{
_FirstName = value;
}
}
}
}
}
C# has two different versions of constants: compile-time
constants and runtime constants. Prefer runtime constants over compile-time
constants. Compile-time constants are slightly faster, but far less
flexible, than runtime constants. Reserve the compile-time constants for when
performance is critical and the value of the constant will never change over
time.
You declare runtime constants with the readonly
keyword. Compile-time constants are declared with the const
keyword:
public const int _Millennium =
2000;
// Compile time constant
public static readonly int _ThisYear = 2004;
// Runtime constant
The differences in the behavior of compile-time and runtime constants follow from how they are accessed. A compile-time constant is replaced with the value of that constant in your object code:
if ( myDateTime.Year == _Millennium )
// Compiles to: if ( myDateTime.Year == 2000 )
Runtime constants are evaluated at runtime. The IL generated when you reference
a read-only constant references the readonly
variable, not the value.
This distinction places several restrictions on when you are allowed to use
either type of constant. Compile-time constants can be used only for primitive
types (built-in integral and floating-point types), enums,
or strings. These primitive types are the only ones that can be replaced with
literal values in the compiler-generated IL. The following construct does not
compile. You cannot initialize a compile-time constant using the new operator,
even when the type being initialized is a value type:
//Does not compile, use readonly
instead:
private const DateTime _classCreation =
new DateTime( 2000, 1, 1, 0, 0, 0 );
readonly values are also constants, in that they
cannot be modified after the constructor has executed. But read-only values are
different, in that they are assigned at runtime. You have much more flexibility
in working with runtime constants. For one thing, runtime constants can be any
type. You must initialize them in a constructor, or you can use an initializer.
You can make readonly values of the
DateTime structures; you cannot create
DateTime values with const.
The most important distinction is that readonly
values are resolved at runtime. The IL generated when you reference a
readonly constant references the
readonly variable, not the value. This difference
has far-reaching implications on maintenance over time. Compile-time constants
generate the same IL as though you've used the numeric constants in your code,
even across assemblies: A constant in one assembly is still replaced with the
value when used in another assembly. Consider the following code in assembly A:
class Constants
{
public static readonly int START
= 10;
public const int END = 20;
}
In another assembly, assembly B, you have code that writes the value of
both constants:
Trace.Writeline( START);
Trace.Writeline( END );
Once all assemblies are compiled, the above trace statements give obvious
outputs: 10 and 20. Time
passes and you release a new version of assembly A with the following changes
class Constants
{
public static readonly int START
= 100;
public const int END = 200;
}
You release assembly A without rebuilding assembly B. You'd expect
that the trace lines would give 100 and
200. Wrong! Trace outputs
100 and 20!
The C# compiler placed the const value of 20 into the Application assembly
instead of a reference to the storage used by END.
Contrast that with the START value. It was declared
as readonly: It gets resolved at runtime. Therefore,
the application assembly makes use of the new value without even recompiling the
application assembly; simply installing an updated version of the A assembly is
enough to change the behavior of all clients using that value. Updating
the value of a public constant should be viewed as an interface change. You must
recompile all code that references that constant. Updating the value of a
read-only constant is an implementation change; it is binary compatible with
existing client code.
Finally, not that known constant values (i.e., const)
can generate slightly more efficient code than the variable accesses necessary
for readonly values. However, any gains are slight
and should be weighed against the decreased flexibility. Be sure to profile
performance differences before giving up the flexibility.
class Item2
{
public const int _Millennium = 2000; // Compile time constant
public readonly double PI = 3.14; // Runtime constant
// The following does not compile
// Error CS0283: The type 'System.DateTime' cannot be declared const
//private const DateTime START_YEAR = new DateTime(2009, 1, 1);
// The following compiles. Uses 'readonly' rather than 'const'
public static readonly DateTime START_YEAR = new DateTime(2009,1,1);
}
Many times in C#, you write functions that take
System.Object parameters because the framework
defines the method signature for you. You likely need to attempt to downcast
those objects to other types, either classes or interfaces. You've got two
choices: Use the as operator or use that old C
standby, the cast. You also have a defensive variant: You can test a conversion
with is and then use as
or casts to convert it.
The correct choice is to use the as
operator whenever you can because it is safer than blindly casting and is more
efficient at runtime. The is and
as operators do not perform any user-defined
conversions. They succeed only if the runtime type matches the sought type; they
never construct a new object to satisfy a request . See method
TestISAndAsVsCast1() below.
The biggest difference between the as operator
and the cast operator is how user-defined conversions are treated. The
as and is operators
examine the runtime type of the object being converted; they do not perform any
other operations. If a particular object is not the requested type or is derived
from the requested type, they fail. Casts, on the other hand, can use conversion
operators to convert an object to the requested type. This includes any built-in
numeric conversions. Casting a long to a short can lose information. See method
TestISAndAsVsCast2() below.
Recall that the as operator does not work on value
types. You're stuck with a cast. But you're not necessarily stuck with the
behaviors of casts. You can use the is statement to remove the chance of
exceptions or conversions
int i = 0;
object o = GetSomeObject();
if (o is int)
i = (int)o;
// Use cast after checking that o is an int
The is operator should be used only when you cannot convert the type using as. Otherwise, it's simply redundant. See TestIsAndAs() method below.
class Item3
{
/// <summary>
/// Illustrates general concept of using is/as vs. cast
/// </summary>
public void TestISAndAsVsCast1()
{
// Get an object instance
object o = GetSomeObject();
// Approach 1. Correct
DateTime? dt = o as DateTime?;
if (dt != null)
Trace.WriteLine(dt.ToString());
// Approach 2. Messy
// With casts you must check null as null can be converted to any reference type using a cast
// You must also catch exceptions.
try
{
DateTime? dt2 = (DateTime?)o;
if (dt2 != null)
{
Trace.WriteLine(dt2.ToString());
}
else
{
// Report null ref exception
}
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
}
// Test method whose return parameter is System.Object
private object GetSomeObject()
{
Nullable<DateTime> dt = DateTime.Now;
return dt;
}
/// <summary>
/// Illustrates that cast can invoke implicit conversions.
/// Remember that user-defined conversion operators operate only on the compile-time type of an
/// object, not on the runtime type.
/// </summary>
public void TestISAndAsVsCast2()
{
// Get an object instance
Person p = new Person();
// Invokes user-defined conversion operator!
try
{
Student s = (Student)p;
if (s != null)
{
Trace.WriteLine(s.StudentID);
}
else
{
// Report null ref exception
}
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
}
/// <summary>
/// The is operator should be used only when you cannot convert the type using as.
/// Otherwise, it's simply redundant
/// </summary>
public void TestIsAndAs()
{
object o = GetSomeObject();
DateTime? dt = null;
// Redundant approach 1
{
if (o is DateTime?)
dt = o as DateTime?;
}
// Redundant approach 2
{
if ((o as DateTime?) != null)
dt = o as DateTime?;
}
// Proper approach
dt = o as DateTime?;
if (dt != null)
{
// Required code ....
}
}
}
namespace Item3NS
{
// Contains an implicit conversion operator that converts from SomeType to DateTime
public class Person
{
public string Name { get; set; }
public static implicit operator Student(Person p)
{
return new Student();
}
}
public class Student
{
public string StudentID { get; set; }
}
}
#if / #endif
blocks have been used to produce different builds from the same source, most
often debug and release variants. #if /
#endif blocks are too easily abused, creating code
that is hard to understand and harder to debug.
C# has the [Conditional] attribute to
indicate whether a method should be called based on an environment setting. It's
a cleaner way to describe conditional compilation than #if
/ #endif. The [Conditional]
attribute is applied at the method level, so it forces you to separate
conditional code into distinct methods. Therefore, use the [Conditional]
attribute instead of #if /
#endif block when you create conditional code blocks.
See ShowFrames_Old() and
ShowFrames_New() methods below.
The [Conditional] attribute can be applied only to
entire methods. In addition, any method with a
[Conditional] attribute must have a return type of
void. You cannot use the [Conditional]
attribute for blocks of code inside methods or with methods that return values.
Note that the [Conditional] attribute does not
affect the code generated for the attributed function,
ShowFrames_New() function, rather, it modifies
the calls to the function. If the DEBUG symbol is
defined, you get this:
public void TestConditional()
{
ShowFrames_New();
Trace.WriteLine("This is a test");
}
If not, you get this:
public void TestConditional()
{
Trace.WriteLine("This is a test");
}
The body of the ShowFrames_New() function is the
same, regardless of the state of the environment variable. Whether the
DEBUG environment variable is defined or not, the
ShowFrames_New() method is compiled and
delivered with the assembly. That might seem inefficient, but the only cost is
disk space. The ShowFrames_New() function does
not get loaded into memory and JITed unless it is called. Its presence in the
assembly file is immaterial.
Note that you can also create methods that depend on more than one environment
variable. When you apply multiple conditional attributes, they are OR-combined.
For example, this version of ShowFrames_New()
would be called when either DEBUG or
TRACE is true:
[ Conditional( "DEBUG" ), Conditional( "TRACE"
) ]
private void ShowFrames_New( )
class Item4
{
public void TestConditional()
{
ShowFrames_New();
Trace.WriteLine("This is a test");
}
/// <summary>
/// Using the #if and #endif pragmas, you've created an empty method in your release builds.
/// The ShowFrames_Old() method gets called in all builds, release and debug. It doesn't
/// do anything in the release builds, but you pay for the method call. You also pay a small
/// cost to load and JIT the empty routine.
/// </summary>
private void ShowFrames_Old()
{
#if DEBUG
StackFrame[] frames = new StackTrace(1).GetFrames();
foreach (StackFrame frame in frames)
Trace.WriteLine(frame.GetMethod().Name);
#endif
}
/// <summary>
/// Using the Conditional attribute, you can isolate functions that should be part of your
/// classes only when a particular environment variable is defined or set to a certain value.
/// The most common use of this feature is to instrument your code with debugging statements.
/// </summary>
[Conditional("DEBUG")]
private void ShowFrames_New()
{
StackFrame[] frames = new StackTrace(1).GetFrames();
foreach (StackFrame frame in frames)
Trace.WriteLine(frame.GetMethod().Name);
}
}
System.Object.ToString() is one of the
most used methods in the .NET environment. The
System.Object version returns the name of the type, which in most cases
can be useless. You should write a reasonable version for all the clients of
your class. Otherwise, you force every user of your class to use the properties
in your class and create a reasonable human-readable representation. This string
representation of your type can be used to easily display information about an
object to users. In fact, this the string representation of objects is
very useful for debugging. When you create more complicated types, you
should implement the more sophisticated
IFormattable.ToString(). IFormattable is the
interface you use when you need to create different forms of string output.
See class Person and method
TestToStringOverride() below.
Note that any implementation of IFormattable.ToString()
is specific to the type, but you must handle certain cases whenever you
implement the IFormattable interface. First, you
must support the general format, "G". Second, you
must support the empty format in both variations: ""
and null. All three format specifiers must return
the same string as your override of the Object.ToString()
method. The .NET Framework calls IFormattable.ToString()
instead of Object.ToString() for every type that
implements IFormattable. If you add support for the
IFormattable interface and do not support these
standard formats, you've broken the automatic string conversions in the .NET
Framework.
Overriding Object.ToString() is the simplest way to
provide a string representation of your classes. You should provide that every
time you create a type. On those rarer occasions when your type is expected to
provide more sophisticated output, you should take advantage of implementing the
IFormattable interface. It provides the standard way
for users of your class to customize the text output for your type. If you leave
these out, your users are left with implementing custom formatters (IFormatProvider).
Those solutions require more code, and because users are outside of your class,
they cannot examine the internal state of the object.
class Item5
{
public void TestToStringOverride()
{
// Create and initialize a Person object
Person p = new Person();
p.FirstName = "Yazan";
p.LastName = "Diranieh";
p.DOB = new DateTime(2000, 1, 1);
p.Address = "Street, City, Country";
// Display its string representation
Trace.WriteLine(p.ToString()); // Output: Diranieh
// Use IFormattable to specify various format string
// Output: "Yazan Diranieh 01/01/2000 00:00:00 Street, City, Country "
IFormattable fomattable = p as IFormattable;
if (fomattable != null)
Trace.WriteLine(fomattable.ToString("FLDA", null));
}
}
namespace Item5NS
{
/// <summary>
/// The ToString() override uses only the LastName. You address this deficiency by
/// implementing the IFormattable interface on your type. IFormattable contains an
/// overloaded ToString() method that lets you specify formatting information for
/// your type.
/// </summary>
internal class Person : IFormattable
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DOB { get; set; }
public string Address { get; set; }
// Override ToString()
public override string ToString()
{
return LastName;
}
#region IFormattable Members
/// <summary>
/// Supported formats are:
/// F: FirstName
/// L: LastName
/// D: DOB
/// A: Address
///
/// The second parameter to IFormattable.ToString() is an object that implements the
/// IFormatProvider interface. This object lets clients provide formatting options
/// that you did not anticipate. No matter how many different formats you support,
/// your users will one day want some format that you did not anticipate. That's why
/// the first few lines of the method look for an object that implements IFormatProvider
/// and delegate the job to its ICustomFormatter. See MSDN for an example of how to
/// implement IFormatProvider
/// </summary>
public string ToString(string format, IFormatProvider formatProvider)
{
// See notes above for an explanation of the following block of code
if (formatProvider != null)
{
ICustomFormatter fmt = formatProvider.GetFormat(this.GetType()) as ICustomFormatter;
if (fmt != null)
return fmt.Format(format, this, formatProvider);
}
StringBuilder sb = new StringBuilder();
// Format string form: {index[,alignment][:formatString]}
foreach (char formatchar in format)
{
switch (formatchar)
{
case 'F':
sb.AppendFormat("{0}\t", FirstName);
break;
case 'L':
sb.AppendFormat("{0}\t", LastName);
break;
case 'D':
sb.AppendFormat("{0:G}\t", DOB);
break;
case 'A':
sb.AppendFormat("{0}\t", Address);
break;
case 'G':
default:
sb.Append(LastName);
break;
}
}
return sb.ToString();
}
#endregion
}
}
Value types or reference types? Structs or classes? It's not as simple as
preferring one over the other. The right choice depends on how you expect to use
the new type. Value types are not polymorphic. They are better suited to storing
the data that your application manipulates. Reference types can be polymorphic
and should be used to define the behavior of your application. Structs
store data. Classes define behavior.
In C#, you declare whether a new type should be a value type or a reference type
using the struct or class
keywords. Value types should be small, lightweight types. Reference types
form your class hierarchy.
To start, MyData is used as the return value from a method:
private MyData _myData;
public MyData Foo()
{
return _myData;
}
// call it:
MyData v = Foo();
TotalSum += v.Value;
If MyData is a value type, the return value gets copied into the storage for
v.
Furthermore, v is on the stack. However, if
MyData is a reference type, you've
exported a reference to an internal variable. You've violated the principle of
encapsulation (see Item 23
Avoid
Returning References to Internal Class Objects)
Or, consider this variant:
private MyData _myData;
public MyData Foo()
{
return _myData.Clone( )
as MyData;
}
// call it:
MyData v = Foo();
TotalSum += v.Value;
Now, v is a copy of the original
_myData. As a reference type, two objects are
created on the heap. You don't have the problem of exposing internal data.
Instead you've created an extra object on the heap. If v is a local variable, it
quickly becomes garbage and Clone() forces you to use runtime type checking. All
in all, it's inefficient.
In general, types that are used to export data through public methods and
properties should be value types. But that's not to say that every type returned
from a public member should be a value type.
Now consider this alternative code snippet to the one shown first:
private MyType _myType;
public IMyInterface Foo()
{
return _myType
as IMyInterface;
}
// call it:
IMyInterface iMe = Foo();
iMe.DoWork( );
The _myType variable is still returned from the
Foo() method. But this time,
instead of accessing the data inside the returned value, the object is accessed
to invoke a method through a defined interface. You're accessing the
MyType
object not for its data contents, but for its behavior. That behavior is
expressed through the IMyInterface interface, which can be implemented by multiple
different types. For this example, MyType should be a reference type, not a
value type. MyType's responsibilities revolve around its behavior, not its data
members. That simple code snippet starts to show you the distinction:
Value types store values, and reference types define behavior.
Now consider this class:
public class C
{
private MyType _a =
new MyType( );
private MyType _b =
new MyType( );
...
}
C var = new C();
How many objects are created? How big are they? It depends. If
MyType is a value
type, you've made one allocation. The size of that allocation is twice the size
of MyType. However, if MyType is a reference type, you've made three
allocations: one for the C object, which is 8 bytes (assuming 32-bit pointers),
and two more for each of the MyType objects that are contained in a
C object.
The difference results because value types are stored inline in an object,
whereas reference types are not. Each variable of a reference type holds a
reference, and the storage requires extra allocation. To drive this point home,
consider this allocation:
MyType [] var = new MyType[ 100 ];
If MyType is a value type, one allocation of 100 times the size of a
MyType
object occurs. However, if MyType is a reference type, one allocation just
occurred. Every element of the array is null. When you initialize each element
in the array, you will have performed 101 allocations—and 101 allocations take
more time than 1 allocation. Allocating a large number of reference types
fragments the heap and slows you down. If you are creating types that are meant
to store data values, value types are the way to go.
The decision to make a value type or a reference type is an important one. It is
a far-reaching change to turn a value type into a class type. Consider this
type:
public struct Employee
{
private string _name;
private int _ID;
private decimal _salary;
...
public void Pay( BankAccount b )
{
b.Balance += _salary;
}
}
One day you decide that there are different classes of Employees: Salespeople
get commissions,
and managers get bonuses. You decide to change the Employee type into a
class
and make method Pay a virtual method.
That breaks much of the existing code that uses your customer
struct. Return by
value becomes
return by reference. Parameters that were passed by value are now passed by
reference. The
behavior of this little snippet changed drastically:
Employee e1 = Employees.Find( "CEO" );
e1.Salary += Bonus;
// Add one time bonus.
e1.Pay( CEOBankAccount );
What was a one-time bump in pay to add a bonus just became a permanent raise as
now e1 is
a reference and changes to e1 will remain persistent.
The documentation for .NET recommends that you consider the size of a type as a
determining
factor between value types and reference types. In reality, a much better factor
is the use
of the type. Types that are simple structures or data carriers are excellent
candidates
for value types. Value types are more efficient in terms of memory management:
There is less
heap fragmentation, less garbage, and less indirection. More important, value
types are copied
when they are returned from methods or properties. There is no danger of
exposing references
to internal structures. But you pay in terms of features. Value types have very
limited support
for common object-oriented techniques. You cannot create object hierarchies of
value types.
You should consider all value types as though they were sealed. You can create
value types that
implement interfaces, but that requires boxing, which Item 17 (Minimize
Boxing and Unboxing) shows causes
performance degradation.
Think of value types as storage containers, not objects in the OO sense.
You'll create more reference types than value types. If you answer yes to all
these questions,
you should create a value type. Compare these to the previous
Employee example:
Is this type's principal responsibility data storage?
Is its public interface defined entirely by properties that access or modify its data members?
Am I confident that this type will never have subclasses?
Am I confident that this type will never be treated polymorphically?
Build low-level data storage types as value types. Build the behavior of your application using reference types. You get the safety of copying data that gets exported from your class objects. You get the memory usage benefits that come with stack-based and inline value storage, and you can utilize standard object-oriented techniques to create the logic of your application. When in doubt about the expected use, use a reference type.
Immutable types are simple: After they are created, they are
constant. You cannot change the object's internal state to make it invalid.
Immutable types are inherently thread safe: Multiple readers can access the same
contents. There is no chance for different threads to see inconsistent views of
the data.
Atomic types are types that describe a single entity. An address is a single
thing, composed of multiple related fields. A change in one field likely means
changes to other fields. A customer type is not an atomic type. A customer type
will likely contain many pieces of information: an address, a name, and one or
more phone numbers. Any of these independent pieces of information might change.
class Address_Mutable and method
TestMutableTypes() illustrate the issues with
mutable types, while class Address_Immutable
and method TestImmutableType() show how to
fix these problems.
In general, to create an immutable type, you need to ensure that there are no
holes that would allow clients to change your internal state. Value types do not
support derived types, so you do not need to defend against derived types
modifying fields. But you do need to watch for any fields in an immutable type
that are mutable reference types. When you implement your constructors for these
types, you need to make a defensive copy of that mutable type. See classes
BookList_Mutable and
BookList_Immutable and method
TestImmutableTypesWithMutableFields().
The complexity of a type dictates which of three strategies you will use to
initialize your immutable type. Defining the reasonable set of constructors is
often the simplest approach. You can also create factory methods to initialize
the structure. Factories make it easier to create common values. The .NET
Framework Color type follows this strategy to
initialize system colors. The static methods
Color.FromKnownColor() and Color.FromName()
return a copy of a Color value that represents the
current value for a given system Color. Third, you
can create a mutable companion class for those instances in which multistep
operations are necessary to fully construct an immutable type. The .NET string
class follows this strategy with the
System.Text.StringBuilder class. You use the
StringBuilder class to create a string using multiple operations. After
performing all the operations necessary to build the string, you retrieve the
immutable string from the StringBuilder.
Immutable types are simpler to code and easier to maintain. Do not blindly
create get and set accessors for every property in your type. Your first choice
for types that store data should be immutable, atomic value types. You easily
can build more complicated structures from these entities.
class Item7
{
/// <summary>
/// This method illustrate some of the issues around using mutable types. After an
/// instance is created and initialized, we modify the address. This code looks harmless
/// enough, but suppose that this fragment is part of a multithreaded program. Any
/// context switch after the city changes and before the country changes would leave
/// the potential for another thread to see an inconsistent view of the data.
///
/// You can still get in trouble even if not writing multithreaded programs. Imagine that
/// the country was invalid and the set threw an exception. You've made only some of the
/// changes you intended, and you've left the system in an invalid state. To fully implement
/// exception safety, you would need to create defensive copies around any code block in
/// which you change more than one field. Thread safety would require adding significant
/// thread-synchronization checks on each property accessor, both sets and gets. Instead,
/// make the Address structure an immutable type. See method TestImmutableType()
/// </summary>
public void TestMutableTypes()
{
// Create and initialize and instance of Address_Mutable
Address_Mutable a1 = new Address_Mutable();
a1.Line1 = "1 Les Champes Elysee";
a1.City = "Paris";
a1.Country = "France";
// Change address
a1.Line1 = "1 Bond Street";
a1.City = "London"; // Country is now invalid
a1.Country = "England"; // Country is now valid
}
/// <summary>
/// Shows the use of immutable types. Note that as soon as a new Address_Immutable
/// object is constructed, its value is fixed for all time. It's exception safe:
/// object 'address' has either its original value or its new value. If an exception
/// is thrown during the construction of the new 'address' object, the original value
/// of 'address' object is unchanged.
/// </summary>
public void TestImmutableType()
{
// Create an immutable object
Address_Immutable address = new Address_Immutable("1 Les Champes Elysee", "Paris", "France");
// Cannot moditfy the state of the 'address' object. Attempting to set any
// field on object 'address' gives error CS0272: "The property or indexer
// 'Address_Immutable.City' cannot be used in this context because the set
// accessor is inaccessible"
// To change 'address', you must re-initialize
address = new Address_Immutable("1 Bond Street", "London", "England");
}
public void TestImmutableTypesWithMutableFields()
{
// Create a list of books
List<Book> lstBooks = new List<Book> { new Book("Book1", "Author1"), new Book("Book2", "Author2") };
// Create a mutable type that returns a mutable reference type
BookList_Mutable bm = new BookList_Mutable(lstBooks);
// Create an immutable type that returns a mutable reference type
BookList_Immutable bim = new BookList_Immutable(lstBooks);
// Modify the book list. Also modified the internals of the supposedly immutable
// 'bm' objects. But does not modify the internal of the immutable 'bim' object
lstBooks[0] = new Book("Book3", "Author3");
// Examine contents of 'bm' and 'bim' and note that the internals of 'bm'
// have changed, while the internals of 'bim' have not changed!
}
}
namespace Item7NS
{
/// <summary>
/// Address_Mutable is a mutable type; its data can be changed after an instance
/// is created. Note the use of 'struct' rather than a 'class' as Address_Mutable
/// is a data storage with no behavior
///
/// Note the call to the default constructor in the constructor initializer.
/// This is required in order to assign a value to an automatically-implemented property
/// from a constructor. Otherwise, you get error CS0188: "The 'this' object cannot be used
/// before all of its fields are assigned to."
/// </summary>
struct Address_Mutable
{
public string Line1 { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
struct Address_Immutable
{
public Address_Immutable(string line, string city, string country) : this()
{
Line1 = line;
City = city;
Country = country;
}
public string Line1 { get; private set; }
public string City { get; private set; }
public string Country { get; private set; }
}
struct BookList_Mutable
{
private readonly List<Book> _lstBooks;
public BookList_Mutable(List<Book> books)
{
// Mutable! _lstBook modified if books is modified
_lstBooks = new List<Book>();
_lstBooks = books;
}
public List<Book> Books { get { return _lstBooks; } }
}
struct BookList_Immutable
{
private readonly List<Book> _lstBooks;
public BookList_Immutable(List<Book> books)
{
// Immutable! Copies values because Book is a struct (value type)
// Therefore, _lstBook is not modified if books is modified
_lstBooks = new List<Book>();
_lstBooks.AddRange(books);
}
public List<Book> Books { get { return _lstBooks; } }
}
struct Book
{
public string Name { get; private set; }
public string Author { get; private set; }
public Book(string name, string auth) : this()
{
Name = name;
Author = auth;
}
}
}
The default .NET system initialization sets all objects to all 0s. There is no way for you to prevent other programmers from creating an instance of a value type that is initialized to all 0s. Make 0 the default value for your type.
One special case is enums. Never create an enum that does not include 0 as a valid choice. All enums are derived from System.ValueType. The values for the enumeration start at 0, but you can modify that behavior. See TestEnums() method below.
class Item8
{
private enum Planet_Bad_NoDefault
{
// Explicitly assign values. Default starts at 0 otherwise.
Mercury = 1,
Venus = 2,
Earth = 3,
Mars = 4,
Jupiter = 5,
Saturn = 6,
Neptune = 7,
Uranus = 8,
Pluto = 9
}
private enum Planet_Good_HasDefault
{
None = 0,
Mercury = 1,
Venus = 2,
Earth = 3,
Mars = 4,
Jupiter = 5,
Saturn = 6,
Neptune = 7,
Uranus = 8,
Pluto = 9
}
/// <summary>
/// In this function, sphere object is 0, which is not a valid value as per the definition
/// of enum Plant. Any code that relies on the (normal) fact that enums are restricted to
/// the defined set of enumerated values won't work. When you create your own values for an
/// enum, make sure that 0 is one of them. If you use bit patterns in your enum, define 0
/// to be the absence of all the other properties.
/// </summary>
public void TestEnums()
{
// bad is zero, which is not valid!
Planet_Bad_NoDefault bad = new Planet_Bad_NoDefault();
string name1 = Enum.GetName(typeof(Planet_Bad_NoDefault), bad); // name is null
// good is 1, which is valid
Planet_Good_HasDefault good = new Planet_Good_HasDefault();
string name2 = Enum.GetName(typeof(Planet_Good_HasDefault), bad); // name = "None"
}
}
C# provides many different functions that determine whether two
different objects are "equal":
public static bool
ReferenceEquals ( object left,
object right );
public static bool Equals(
object left, object
right );
public virtual bool Equals(
object right);
public static bool operator==(
MyClass left, MyClass right );
The general rules regrinding these functions are: You should never redefine
the first two static functions. You'll often create your own instance
Equals() method to define the semantics of your
type, and you'll occasionally override operator==(),
but only for performance reasons in value types. Furthermore, there are
relationships among these four functions, so when you change one, you can affect
the behavior of the others.
Recall that two variables of a reference type are equal if they refer to the
same object, referred to as object identity. Two variables of a value type are
equal if they are the same type and they contain the same contents. See
methods TestReferenceEquality() and
TestObjectEquals() below.
Before discussing the methods you will override, you need to make sure that your
definition and implementation of 'equality' are consistent with other
programmers' expectations. This means that you need to keep in mind the
mathematical properties of equality: Equality is reflexive, symmetric, and
transitive. The reflexive property means that any object is equal to itself.
No matter what type is involved, a == a is always
true. The symmetric property means that order does not matter: If
a == b is true,
b == a is also true. If
a == b is false,
b == a is also false.
The last property is that if a == b and
b == c are both true, then a ==
c must also be true. That's the transitive property. See notes for
TestEquals() method below.
Anytime you create a value type, redefine operator==().
The reason is exactly the same as with the instance
Equals() function. The default version uses reflection to compare the
contents of two value types. That's far less efficient than any implementation
that you would write, so write your own.
class Item9
{
/// <summary>
/// This is a function that should never be redefined. It does the right thing.
/// Object.ReferenceEquals() returns true if two variables refer to the same object—that is,
/// the two variables have the same object identity. Whether the types being compared are
/// reference types or value types, this method always tests object identity, not object
/// contents. Yes, that means that ReferenceEquals() always returns false when you use it
/// to test equality for value types. Even when you compare a value type to itself,
/// ReferenceEquals() returns false. This is due to boxing as ReferenceEquals operates
/// only on reference types, hence when value types are used with ReferenceEquals, boxing
/// creates new reference obejcts
/// </summary>
public void TestReferenceEquality()
{
// Reference types
Order o1 = new Order(1);
Order o2 = new Order(2);
bool b1 = object.ReferenceEquals(o1, o1); // TRUE
bool b2 = object.ReferenceEquals(o1, o2); // FALSE
// Reference types. String is a special case. Two strings having the same string
// always refer to the same object
string s1 = "Test1";
string s2 = "Test1";
b1 = object.ReferenceEquals(s1, s1); // TRUE
b2 = object.ReferenceEquals(s1, s2); // TRUE
// Value types
int n1 = 10;
int n2 = 10;
b1 = object.ReferenceEquals(n1, n1); // FALSE
b2 = object.ReferenceEquals(n1, n2); // FALSE
}
/// <summary>
/// This is the second function that you should never redefine. This method tests
/// whether two variables are equal when you don't know the runtime type of the two
/// arguments.
///
/// Recall that value types and reference types are instances of System.Object. So how
/// does this method test the equality of two variables, without knowing their type,
/// when equality changes its meaning depending on the type? The answer is simple:
/// This method delegates that responsibility to one of the types in question. The
/// static Object.Equals() method. For now, understand that static Equals() uses the
/// instance Equals() method of the left argument to determine whether two objects are
/// equal.
///
/// </summary>
public void TestObjectEquals()
{ }
/// <summary>
/// The default Object.Equals() function behaves exactly the same as Object.ReferenceEquals().
/// However, System.ValueType does override Object.Equals(). Two variables of a value type
/// are equal if they are the same type and they have the same contents. ValueType.Equals()
/// implements that behavior. Unfortunately, ValueType.Equals() does not have an efficient
/// implementation. as it must compare all the member variables in any derived type, without
/// knowing the runtime type of the object. In C#, that means using reflection. As you'll see
/// in Item 44, there are many disadvantages to reflection, especially when performance is
/// a goal. Equality is one of those fundamental constructs that gets called frequently in
/// programs, so performance is a worthy goal. Under almost all circumstances, you can write
/// a much faster override of Equals() for any value type. The recommendation is simple:
///
/// For Value Types:
/// ALWAYS CREATE AN OVERRIDE OF ValueType.Equals() WHENEVER YOU CREATE A VALUE TYPE.
///
/// For Reference Types:
/// OVERRIDE THE INSTANCE Equals() FUNCTION ONLY WHEN YOU WANT TO CHANGE THE DEFINED SEMANTICS
/// FOR A REFERENCE TYPE.
///
/// For reference types, your instance method needs to follow a certain pattern to avoid
/// strange surprises for users of your class. See Equals() override in class Order below
///
/// Note that overriding Equals() means that you should write an override for GetHashCode().
/// See Item 10 for details.
///
/// </summary>
public void TestEquals()
{ }
}
namespace Item9NS
{
public class Order
{
public int OrderID { get; set; }
public Order(int id) { OrderID = id; }
public void ProcessOrder() { }
/// <summary>
/// Equals() should never throw exceptions—it doesn't make much sense. Two variables
/// are or are not equal; there's not much room for other failures. Just return false
/// for all failure conditions, such as null references or the wrong argument types.
///
/// The check using GetType() determines whether the two objects being compared are the
/// same type. The exact form is important. First, notice that it does not assume that
/// 'this' is of type Order; it calls this.GetType(). The actual type might be a class
/// derived from Order. Second, the code checks the exact type of objects being compared.
/// It is not enough to ensure that you can convert the right-side parameter to the current
/// type.
/// </summary>
public override bool Equals(object rhs)
{
// check that right-hand side of the equality is not null. For the the 'this' pointer
// it is never null in C# methods as the CLR throws an exception before calling any
// method on a null reference
if (rhs == null)
return false;
// Check we are not comparing the same object references
if (object.ReferenceEquals(this, rhs))
return true;
// Checks that the compared objects are of the same type.
if (this.GetType() != rhs.GetType())
return false;
// Compare this type's contents here:
return (this.OrderID == (rhs as Order).OrderID);
}
}
}
GetHashCode() is one function that you
should avoid writing. GetHashCode() is used in
one place only: to define the hash value for keys in a hash-based collection,
typically the Hashtable or Dictionary containers. If you are defining a
type that will never be used as the key in a container, then there is no need to
write GetHashCode().
The following discussion applies only if you ever need to create a type that is
meant to be used as a hash table key. Hash-based containers use hash codes to
optimize searches. Every object generates an integer value called a hash code.
Objects are stored in buckets based on the value of that hash code. To search
for
an object, you request its key and search just that one bucket. In .NET, every
object has a hash code, determined by System.Object.GetHashCode().
Any overload
of GetHashCode() must follow these three rules:
If two objects are equal (as defined by operator==), they must generate the same hash value. Otherwise, hash codes can't be used to find objects in containers.
For any object A, A.GetHashCode() must be an instance invariant. No matter what methods are called on A, A.GetHashCode() must always return the same value. That ensures that an object placed in a bucket is always in the right bucket.
The hash function should generate a random distribution among all integers for all inputs. That's how you get efficiency from a hash-based container.
Writing a correct and efficient hash function requires extensive knowledge of
the
type to ensure that rule 3 is followed. The versions defined in
System.Object
and System.ValueType do not have that advantage. These versions must provide the
best
default behavior with almost no knowledge of your particular type.
Object.GetHashCode() uses an internal field in the
System.Object class to
generate
the hash value. Each object created is assigned a unique object key, stored as
an
integer, when it is created. These keys start at 1 and increment every time a
new
object of any type gets created. The object identity field is set in the
System.Object
constructor and cannot be modified later. Object.GetHashCode() returns this
value
as the hash code for a given object.
GetHashCode() for reference types:
If two objects are equal, Object.GetHashCode() returns the same hash value,
unless
you've overridden operator==. System.Object's version of
operator== tests
object
identity. GetHashCode() returns the internal object identity field. It works.
However, if you've supplied your own version of operator==, you must also supply
your own version of GetHashCode() to ensure that the first rule is followed. See
Item 9 (Understand
the Relationships Among
ReferenceEquals(), static Equals(),
instance Equals(), and
operator==) for details on equality.
The second rule is followed: After an object is created, its hash code never
changes.
The third rule, a random distribution among all integers for all inputs, does
not
hold. A numeric sequence is not a random distribution among all integers unless
you
create an enormous number of objects. The hash codes generated by
Object.GetHashCode()
are concentrated at the low end of the range of integers.
This means that Object.GetHashCode() is correct but not efficient. If you create a
hashtable based on a reference type that you define, the default behavior from
System.Object is a working, but slow, hashtable. When you create reference types
that are meant to be hash keys, you should override GetHashCode() to get a better
distribution of the hash values across all integers for your specific type.
If you're going to build a better hash code, you need to place some constraints
on your
type. Examine the three rules again, this time in the context of building a
working
implementation of GetHashCode().
First, if two objects are equal, as defined by operator==, they must return
the same hash value. Any property or data value used to generate the hash
code must also participate in the equality test for the type. Obviously, this means that the
same
properties used for equality are used for hash code generation. It's possible to
have
properties participate in equality that are not used in the hash code
computation.
The second rule is that the return value of GetHashCode() must be an instance
invariant.
See TestHashCode1() which creates objects whose hash code is not instance
invariant.
The third rule says that GetHashCode() should generate a random distribution
among all
integers for all inputs. A common and successful algorithm is to
XOR all the
return
values from GetHashCode() on all fields in a type. If your type contains some
mutable
fields, exclude those fields from the calculations.
To summarize, GetHashCode() has very specific requirements: Equal objects must
produce
equal hash codes, and hash codes must be object invariants and must produce an
even
distribution to be efficient. All three can be satisfied only for immutable
types.
For other types, rely on the default behavior, but understand the pitfalls.
class Item10
{
public void TestHashCode1()
{
// Create an object and show its hash code
Customer c1 = new Customer("Test");
Trace.WriteLine("c1 hash code:" + c1.GetHashCode()); // c1 hash code:-354185577
// Changing the state of c1 changes its hash code! This violates the second
// rule which states that GetHashCode() must be instance invariant
c1.Name = "Test2";
Trace.WriteLine("c1 hash code:" + c1.GetHashCode()); // c1 hash code:-1556460257
}
}
namespace Item10NS
{
public class Customer
{
public string Name { get; set; }
public Customer(string name)
{
Name = name;
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
}
The C# foreach statement generates the
best iteration code for any collection you have. C# code runs in a safe, managed
environment. Every memory location is checked, including array indexes.
Therefore, when you iterate collections, use foreach
instead of other looping constructs. Consider the following C++ style loop:
int len = foo.Length;
for (int index = 0;
index < len; index++ )
Trace.WriteLine( index );
By hosting the Length variable out of the loop, you
make a change that hinders the JIT compiler's chance to remove range checking
inside the loop. One of the CLR guarantees is that you cannot write code that
overruns the memory that your variables own. The runtime generates a test of the
actual array bounds (not your len variable) before
accessing each particular array element. You get one bounds check for the price
of two.
foreach adds other language benefits for you. The
loop variable is read-only: You can't replace the objects in a collection using
foreach. Also, there is explicit casting to the
correct type. If the collection contains the wrong type of objects, the
iteration throws an exception. foreach gives you
similar benefits for multidimensional arrays. Suppose that you are creating a
chess board. You would write these two fragments:
Square[,] _theBoard = new
Square[ 8, 8 ];
for (int i = 0; i < _theBoard.GetLength(
0 ); i++ )
for(int
j = 0; j < _theBoard.GetLength( 1 ); j++ )
_theBoard[ i, j ].PaintSquare( );
Instead, you can simplify painting the board this way:
foreach(Square sq
in _theBoard )
sq.PaintSquare( );
To summarize, foreach is a very versatile statement.
It generates the right code for upper and lower bounds in arrays, iterates
multidimensional arrays, coerces the operands into the proper type (using the
most efficient construct), and, on top of that, generates the most efficient
looping constructs. It's the best way to iterate collections. With it, you'll
create code that is more likely to last, and it's simpler for you to write in
the first place. It's a small productivity improvement, but it adds up over
time.
Classes often have more than one constructor. Over time, it's easy for the
member variables and the constructors to get out of synch. The best way to make
sure this doesn't happen is to initialize variables where you declare them
instead of in the body of every constructor. You should utilize the
initializer syntax for both static and instance variables.
Initializers execute before the base class constructor for your type executes,
and they are executed in the order the variables are declared in your class.
Regardless of the number of constructors you eventually add to your types,
member variables initialized via variables initializers will be initialized
properly. The compiler generates code at the beginning of each constructor to
execute all the initializers you have defined for your instance member
variables. When you add a new constructor, all member variables get initialized.
Similarly, if you add a new member variable, you do not need to add
initialization code to every constructor; initializing the variable where you
define it is sufficient. Equally important, the initializers are added to the
compiler-generated default constructor. The C# compiler creates a default
constructor for your types whenever you don't explicitly define any
constructors.
Using initializers is the simplest way to avoid uninitialized variables in your
types, but it's not perfect. In three cases, you should not use the initializer
syntax:
Do not use variable initializers when you are initializing the object to 0, or null. The default system initialization sets everything to 0 for you before any of your code executes. The system-generated 0 initialization is done at a very low level using the CPU instructions to set the entire block of memory to 0. Any extra 0 initialization on your part is superfluous.
Do not use variable initializers when you create multiple initializations for the same object. You should use the initializer syntax only for variables that receive the same initialization in all constructors. See class MyClass2. By the way, the proper way to fix MyClass2 is to use constructor chaining (14. Use Constructor Chaining). See MyClass3.
To summarize, variable initializers are the simplest way to ensure that the member variables in your type are initialized regardless of which constructor is called. The initializers are executed before each constructor you make for your type. Using this syntax means that you cannot forget to add the proper initialization when you add new constructors for a future release.
namespace Item12NS
{
/// <summary>
/// Illustrates use of variables initializers
/// </summary>
internal class MyClass
{
// Declare a collection and initialize it
List<int> _lstNumbers = new List<int>();
}
/// <summary>
/// When you create a new MyClass2, specifying the size of the collection, you create
/// two lists: The list initialized via variable initializer is immediately garbage as
/// the variable initializer executes before every constructor. The constructor body
/// creates the second list. See MyClass3 which utilizes constructor chaining to fix
/// MyClass2
/// </summary>
internal class MyClass2
{
List<int> _lst = new List<int>();
public MyClass2()
{ /* Empty Implementation */ }
public MyClass2(int nCapacity)
{
_lst = new List<int>(nCapacity);
}
}
/// <summary>
/// Fixes MyClass2 by utilizing constructor chaining
/// </summary>
internal class MyClass3
{
List<int> _lst = null;
public MyClass3() : this(0)
{ /* Empty Implementation */ }
public MyClass3(int nCapacity)
{
_lst = (nCapacity == 0)? new List<int>() : new List<int>(nCapacity);
}
}
}
C# lets you use static initializers and a static constructor to initialize static member variables. A static constructor is a special function that executes before any other methods, variables, or properties defined in that class are accessed. You use this function to initialize static variables, enforce the singleton pattern, or perform any other necessary work before a class is usable. You should not use your instance constructors, some special private function, or any other idiom to initialize static variables.
As with instance initialization, you can use the initializer syntax
as an alternative to the static constructor. If you simply need to
allocate a static member, use the initializer syntax. When you have more
complicated logic to initialize static member variables, create a static
constructor.
As with instance initializers, the static initializers are called before any
static constructors are called. And, yes, your static initializers execute
before the base class's static constructor. Implementing the singleton pattern
in C# is the most frequent use of a static constructor. Make your instance
constructor private, and add an initializer. See class
MySingleton below.
The CLR calls your static constructor automatically when your type is first
loaded into an application space. You can define only one static constructor,
and it must not take any arguments. Because static constructors are called by
the CLR, you must be careful about exceptions generated in them. If you let an
exception escape a static constructor, the CLR will terminate your program.
Exceptions are the most common reason to use the static constructor instead of
static initializers.
namespace Item13NS
{
/// <summary>
/// Illustrates the use of a static initializer to implement a singleton.
/// An alternative implementation is to move the static initializer to a static
/// constructor, in case you have more complex logic to initialize the singleton,
/// or where you may need to catch any generated exceptions.
/// </summary>
internal class MySingleton
{
// Static initializer.
private static readonly MySingleton _instance = new MySingleton();
private MySingleton()
{ /* Empty Implementation */ }
public static MySingleton SingleInstance { get { return _instance; } }
}
}
When you find that multiple constructors contain the same logic,
factor that logic into a common constructor (and not a private method)
using constructor initializers. Constructor initializers allow one constructor
to call another constructor. The C# compiler recognizes the constructor
initializer as special syntax and removes the duplicated variable initializers
and the duplicated base class constructor calls. The result is that your final
object executes the minimum amount of code to properly initialize the object.
You also write the least code by delegating responsibilities to a common
constructor. See MyClass class below.
C# does not support default parameters, which would be the preferred C++
solution to this problem. You must write each constructor that you support as a
separate function. With constructors, that can mean a lot of duplicated code.
Use constructor chaining instead of creating a common utility routine.
Now look at MyClass_Bad class below. That
version looks the same as MyClass class, but it
generates far less efficient object code. The compiler adds code to perform
several functions on your behalf in constructors. It adds statements for all
variable initializers (see Item 12
Prefer
Variable Initiailzers to Assignment Statements). It calls the base class
constructor. When you write your own common utility function, the compiler
cannot factor out this duplicated code.
Another important factor to favour the use of constructor initializers is
read-only. The use of a common private function that factors out common
constructor logic to initialize readonly constants
generates compiler error (see MyClass_Bad).
Again, C# constructor initializers provide a better alternative.
As a reminder, here is the order of operations for constructing the first
instance of a type:
Static variable storage is set to 0.
Static variable initializers execute.
Static constructors for the base class execute.
The static constructor executes.
Instance variable storage is set to 0.
Instance variable initializers execute.
The appropriate base class instance constructor executes.
The instance constructor executes.
Subsequent instances of the same type start at step 5 because the class initializers execute only once. Also, steps 6 and 7 are optimized so that constructor initializers cause the compiler to remove duplicate instructions.
namespace Item14NS
{
/// <summary>
/// Illustrates usage of constructor initializers
/// </summary>
class MyClass
{
private List<int> _lstNumbers = null;
private string _strName = null;
// Constructors
public MyClass(string name, int capacity)
{
_lstNumbers = (capacity > 0) ? new List<int>(capacity) : new List<int>();
_strName = name;
}
public MyClass() : this( string.Empty, 0)
{ /* Empty Implementation */ }
public MyClass(string name) : this( name, 0 )
{ /* Empty Implementation */ }
public MyClass( int capacity): this( string.Empty, capacity)
{ /* Empty Implementation */ }
}
class MyClass_Bad
{
private List<int> _lstNumbers = null;
private string _strName = null;
private readonly int _nValue;
// Constructors
public MyClass_Bad(string name, int capacity)
{
ConstructorCommon(name, capacity);
}
public MyClass_Bad()
{
ConstructorCommon( string.Empty, 0);
}
public MyClass_Bad(string name)
{
ConstructorCommon( name, 0 );
}
public MyClass_Bad(int capacity)
{
ConstructorCommon(string.Empty, capacity);
}
private void ConstructorCommon(string name, int capacity)
{
_lstNumbers = (capacity > 0) ? new List<int>(capacity) : new List<int>();
_strName = name;
// error CS0191: A readonly field cannot be assigned to (except in a constructor
// or a variable initializer)
// _nValue = -1;
}
}
}
Types that use unmanaged system resources should be explicitly
released using the Dispose() method of the
IDisposable interface. Therefore, anytime you use
types that have a Dispose() method, it's your responsibility to release those
resources by calling Dispose(). The best way to
ensure that Dispose() always gets called is to
utilized the using statement or a
try/finally block.
All types that own unmanaged resources implement the
IDisposable interface. In addition, they defensively create a finalizer
for those times when you forget to dispose properly. See
TestResourceCleanup1(),
TestResourceCleanup2() and
TestResourceCleanup3() methods below.
Whenever you use one disposable object in a function, the
using clause is the simplest method to use to ensure that objects get
disposed of properly. The using statement generates
a try / finally block
around the object being allocated.
Note that the using statement works only if the compile-time type supports the
IDisposable interface. You cannot use it with
arbitrary objects. A quick defensive as clause is
all you need to safely dispose of objects that might or might not implement
IDisposable:
//Object may or may not support
IDisposable.
object obj = Factory.CreateResource( );
using ( obj as
IDisposable )
{
Console.WriteLine( obj.ToString( ));
}
If obj implements IDisposable,
the using statement generates the cleanup code. If
not, the using statement degenerates to
using(null), which is safe but doesn't do anything.
Every using statement creates a new nested
try / finally block. I
find that an ugly construct, so when I allocate multiple objects that implement
IDisposable, I prefer to write my own
try / finally blocks.
See TestResourceCleanup4() below method.
class Item15
{
/// <summary>
/// Illustrates that two disposable objects (reader and writer) are not properly
/// cleaned up. Both of these objects remain in memory until their finalizers are
/// called. See TestResourceCleanup2() which adds clean up code
/// </summary>
public void TestResourceCleanup1()
{
// Create two disposable streams
StreamReader reader = new StreamReader(@"D:\Projects\EffectiveCS\EffecitveCS\ResourceManagement\Item12.cs");
StreamWriter writer = new StreamWriter(@"D:\Projects\EffectiveCS\EffecitveCS\ResourceManagement\Item12_Copy.cs");
// Use the stream.
string strContents = reader.ReadToEnd();
writer.Write(strContents);
// No clean up code!
}
/// <summary>
/// Improves TestResourceCleanup1 by cleaning up. However, if exceptions are thrown
/// while creating or using the streams, calls to Dispose will never be happen.
/// See TestResourceCleanup3() which fixes this problem through the 'using' statement
/// </summary>
public void TestResourceCleanup2()
{
// Create two disposable streams
StreamReader reader = new StreamReader(@"D:\Projects\EffectiveCS\EffecitveCS\ResourceManagement\Item12.cs");
StreamWriter writer = new StreamWriter(@"D:\Projects\EffectiveCS\EffecitveCS\ResourceManagement\Item12_Copy.cs");
// Use the stream.
string strContents = reader.ReadToEnd();
writer.Write(strContents);
// Clean up code
reader.Dispose();
writer.Dispose();
}
/// <summary>
/// Improves TestResourceCleanup2 through the use of 'using' statements. You allocate
/// an object inside a using statement, and the C# compiler generates a try/finally
/// block around each object
/// </summary>
public void TestResourceCleanup3()
{
// Create two disposable streams
using (StreamReader reader = new StreamReader(@"D:\Projects\EffectiveCS\EffecitveCS\ResourceManagement\Item12.cs"))
{
using (StreamWriter writer = new StreamWriter(@"D:\Projects\EffectiveCS\EffecitveCS\ResourceManagement\Item12_Copy.cs"))
{
// Use the stream.
string strContents = reader.ReadToEnd();
writer.Write(strContents);
} // calls writer.Dispose()
} // calls reader.Dispose()
}
/// <summary>
/// More readable alternative to TestResourceCleanup() that uses try/finally blocks
/// </summary>
public void TestResourceCleanup4()
{
StreamReader reader = null;
StreamWriter writer = null;
try
{
// Create two disposable streams
reader = new StreamReader(@"D:\Projects\EffectiveCS\EffecitveCS\ResourceManagement\Item12.cs");
writer = new StreamWriter(@"D:\Projects\EffectiveCS\EffecitveCS\ResourceManagement\Item12_Copy.cs");
// Use the stream.
string strContents = reader.ReadToEnd();
writer.Write(strContents);
}
finally
{
// Clean up code
if (reader != null) reader.Dispose();
if (writer != null) writer.Dispose();
}
}
/// <summary>
/// Improper use of 'using'. The StreamReader object never gets disposed if the
/// StreamWriter() constructor throws an exception. You must make sure that any
/// objects that implement IDisposable are allocated inside the scope of a using
/// block or a try block. Otherwise, resource leaks can occur.
/// </summary>
public void TestResourceCleanup5()
{
// Create two disposable streams
StreamReader reader = new StreamReader(@"D:\Projects\EffectiveCS\EffecitveCS\ResourceManagement\Item12.cs");
StreamWriter writer = new StreamWriter(@"D:\Projects\EffectiveCS\EffecitveCS\ResourceManagement\Item12_Copy.cs");
using (reader as IDisposable)
using (writer as IDisposable)
{
// Use the stream.
string strContents = reader.ReadToEnd();
writer.Write(strContents);
}
}
}
You can introduce serious performance drains on your program by
creating an excessive number of reference objects that are local to your
methods. So do not overwork the garbage collector. You can follow some
simple techniques to minimize the amount of work that the Garbage Collector
needs to do on your program's behalf. All reference types, even local
variables, are allocated on the heap. Every local variable of a reference type
becomes garbage as soon as that function exits. One very common bad practice is
to allocate GDI objects in a Windows paint handler:
protected override void
OnPaint( PaintEventArgs e )
{
// Bad. Created the same font every
paint event.
using ( Font MyFont =
new Font( "Arial", 10.0f ))
{
e.Graphics.DrawString(
DateTime.Now.ToString(),
MyFont, Brushes.Black,
new PointF( 0,0 ));
}
base.OnPaint( e );
}
OnPaint() gets called frequently. Every
time it gets called, you create another Font object
that contains the exact same settings. The Garbage Collector needs to clean
those up for you every time. That's incredibly inefficient. Instead, promote the
Font object from a local variable to a member
variable. Reuse the same font each time you paint the window:
private readonly Font _myFont
= new Font( "Arial", 10.0f );
protected override void OnPaint( PaintEventArgs e )
{
e.Graphics.DrawString( DateTime.Now.ToString( ),
_myFont, Brushes.Black, new
PointF( 0,0 ));
base.OnPaint( e );
}
Approach one: promote local variables to member variables when
they are reference types (value types don't matter) and they will be used in
routines that are called very frequently. The font in the paint routine makes an
excellent example. Only local variables in routines that are frequently
accessed are good candidates. Infrequently called routines are not. You're
trying to avoid creating the same objects repeatedly, not turn every local
variable into a member variable
The static property Brushes.Black, used earlier illustrates another technique
that you should use to avoid repeatedly allocating similar objects.
Approach two: create static member variables for commonly used instances of the
reference types you need.
The first approach of creating a black brush as a member of each of your types
helps, but it doesn't go far enough. Programs might create dozens of windows and
controls, and would create dozens of black brushes. The .NET Framework designers
anticipated this and created a single black brush for you to reuse whenever you
need it. The Brushes class contains a number of static
Brush objects, each with
a different common color. Internally, the Brushes class uses a lazy evaluation
algorithm to create only those brushes you request.
A simplified implementation looks like this:
private static Brush _blackBrush;
public static Brush Black
{
get
{
// Create a brush once and only when needed
if
( _blackBrush == null )
_blackBrush = new SolidBrush( Color.Black );
return _blackBrush;
}
}
The last technique involves building the final value for immutable types. The
System.String class is immutable: After you construct a string, the contents
of that string cannot be modified. Whenever you write code that appears to
modify the contents of a string, you are actually creating a new
string object
and leaving the old string object as garbage. This seemingly innocent practice:
// The += method on the string class creates a new string object and returns that string
string msg = "Hello, ";
msg += thisUser.Name;
// Creates a new string
msg += ". Today is ";
// Creates a new string
msg += System.DateTime.Now.ToString();
// Creates a new string
is just as inefficient as if you had written this:
string msg = "Hello, ";
// Not legal, for illustration only:
string tmp1 = new String( msg + thisUser.Name );
string msg = tmp1; // "Hello " is garbage.
string tmp2 = new String( msg + ". Today is " );
msg = tmp2; // "Hello <user>" is garbage.
string tmp3 = new String( msg + DateTime.Now.ToString( ) );
msg = tmp3; // "Hello <user>. Today is " is garbage
For simple constructs such as the previous one you can use the
string.Format()
method, or for more complicated string operations, you can use
StringBuilder().
Therefore, the three techniques that you can use to avoid creating excessive
objects are:
Consider promoting local variables to member variables.
Create static objects of the most common instances of your types
Consider creating mutable builder classes for immutable types.
Note: Many of limitations discussed below are no longer applicable with the
introduction of generics in C# 2.0. However, it makes sense to be aware of the
issues discussed below to further help you minimize boxing and unboxing.
Value types are containers for data. They are not polymorphic types. On the
other
hand, the .NET Framework was designed with a single reference type,
System.Object,
at the root of the entire object hierarchy. These two goals are at odds. The
.NET
Framework uses boxing and unboxing to bridge the gap between these two goals.
Boxing places a value type in an untyped reference object to allow the value
type to be used where a reference type is expected. Unboxing extracts a copy of
that value type from the box. Boxing and unboxing are necessary for you to use value
types where the System.Object type is expected. But boxing and unboxing are
always
performance-robbing operations. Sometimes, when boxing and unboxing also create
temporary copies of objects, it can lead to subtle bugs in your programs.
Avoid boxing and unboxing when possible.
Boxing converts a value type to a reference type. A new reference object,
the box, is allocated on the heap, and a copy of the value type is stored inside
that reference object. The box contains the copy of the value type object and
duplicates
the interfaces implemented by the boxed value type. When you need to retrieve
anything from the box, a copy of the value type gets created and returned.
That's
the key concept of boxing and unboxing: A copy of the object goes in the
box, and another gets created whenever you access what's in the box.
Boxing and unboxing is that it happens automatically. The compiler generates the
boxing and unboxing statements whenever you use a value type where a reference
type, such as System.Object is expected. Even a simple statement such as this
performs boxing:
Console.WriteLine("A few numbers:{0}, {1}, {2}", 25, 32, 50);
To avoid this particular penalty, you should convert your types to
string
instances
yourself before you send them to WriteLine():
Console.WriteLine("A few numbers:{0}, {1}, {2}",
25.ToString(), 32.ToString(), 50.ToString());
First rule to avoid boxing: watch for implicit conversions to
System.Object.
Another common case in which you might inadvertently substitute a value type for
System.Object is when you place value types in .NET 1.x collections. This
incarnation
of the .NET Framework collections store references to System.Object instances.
Anytime you add a value type to a collection, it goes in a box. Anytime you
remove
an object from a collection, it gets copied from the box. Taking an object out
of
the box always makes a copy. That introduces some subtle bugs in your
application.
See TestBoxingUnboxing1() and
TestBoxingUnboxing2() methods below.
class Item17
{
/// <summary>
/// Illustrates boxing and unboxing. Person is a value type; it gets placed in a box
/// before being stored in the ArrayList. That makes a copy. Then another copy gets
/// made when you remove the Person object to access the Name property to change.
/// All you did was change the copy. In fact, a third copy was made to call the
/// ToString() function through the al[0] object.
/// </summary>
public void TestBoxingUnboxing1()
{
// Create and initialize a value type
Person p = new Person();
p.Name = "Old Name";
// Use a Person object in a .NET 1.x collection. Boxing takes place automatically.
// A COPY OF P IS PLACED IN A NEW OBJECT
ArrayList al = new ArrayList();
al.Add(p);
// Try to change the name.
Person pCopy = (Person)al[0]; // Unboxing creates a copy of Person at index 0
pCopy.Name = "New Name";
// Now write contents of array list
Trace.WriteLine(al[0].ToString()); // Writes "Old Name"
}
/// <summary>
/// The box reference type implements all the interfaces implemented by the original
/// Person2 object. That means no copy is made, but you call the IPersonName.Name
/// method on the box, which forwards the request to the boxed value type. Creating
/// interfaces on your value types enables you to reach inside the box to change the
/// value stored in the collection
/// </summary>
public void TestBoxingUnboxing2()
{
// Create and initialize a value type
Person2 p = new Person2("Old Name");
// Use a Person object in a .NET 1.x collection. Boxing takes place automatically.
// A COPY OF P IS PLACED IN A NEW OBJECT
ArrayList al = new ArrayList();
al.Add(p);
// Try to change the name. Use the interface, and not the type
IPerson ip = (IPerson)al[0]; // Does not unbox
ip.Name = "New Name";
// Now write contents of array list
Trace.WriteLine(al[0].ToString()); // Writes "New Name"
}
}
namespace Item17NS
{
struct Person
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
interface IPerson
{
string Name { get; set; }
}
struct Person2 : IPerson
{
public Person2(string name) : this()
{
Name = name;
}
#region IPerson Members
public string Name { get; set; }
#endregion
public override string ToString()
{
return Name;
}
}
}
This item has also been discussed in under Programming / Data Structures
&
Collections / System.Object chapter.
The following is a summary of how to implement IDispoable across a class
hierarchy:
The root base class in the class hierarchy should implement the
IDisposable
interface
to free resources. This type should also add a finalizer as a defensive
mechanism.
Both of these routines delegate the work of freeing resources to a virtual
method
that derived classes can override for their own resource-management needs. The derived
classes need override the virtual method only when the derived class must free
its own
resources and it must remember to call the base class version of the function.
The implementation of your IDisposable.Dispose() method is responsible for four
tasks:
Freeing all unmanaged resources.
Freeing all managed resources (this includes unhooking events.)
Setting a state flag to indicate that the object has been disposed. You need to check this state and throw ObjectDisposed exceptions in your public methods, if any get called after disposing of an object.
Suppressing finalization. You call GC.SuppressFinalize(this) to accomplish this task.
When writing disposable objects, the most important recommendation for any
method
associated with disposal or cleanup is that you should be releasing
resources only.
Do not perform any other processing during a dispose method. You can introduce
serious
complications to object lifetimes by performing other processing in your
Dispose
or
finalize methods. Finalizers should do nothing but clean up unmanaged resources.
If a
finalizer somehow makes an object reachable again, it has been resurrected. See
class BadClass below
In a managed environment, you do not need to write a finalizer for every type
you create; You implement IDisposable
only for types that store unmanaged types or when your type contains members
that implement IDisposable. Even if you need only the
IDisposable interface, not a finalizer, implement the entire pattern. Otherwise,
you
limit your derived classes by complicating their implementation of the standard
Dispose idiom.
class Item18
{
/// <summary>
/// The following code creates a derived class that allocates an unmanaged resource
/// but does not explicitly call Dispose. Note in the output window how the finalization
/// code takes care of freeing unmanaged resources. The following output is displayed
/// when the program exits:
///
/// MyBaseResource.~MyBaseResource
/// MyDerivedResource.Dispose(False)
/// MyDerivedResource: Freeing unmanaged resources
/// MyBaseResource.Dispose(False)
/// MyBaseResource: Freeing unmanaged resources
///
/// </summary>
public void TestDisposable1()
{
// Allocate a class that uses some unmanaged resource
MyDerivedResource obD = new MyDerivedResource();
// Do something with the unmanaged resource
obD.foo();
}
/// <summary>
/// The following code creates the same derived class that allocates an unmanaged resource
/// and explicitly calls Dispose. Now note in the output window that the finalization code
/// does not get executed. The following output is displayed when Dispose is called on obD:
///
/// MyBaseResource.Dispose()
/// MyDerivedResource.Dispose(True)
/// MyDerivedResource: Freeing managed resources
/// MyBaseResource.Dispose(True)
/// MyBaseResource: Freeing managed resources
///
/// </summary>
public void TestDisposable2()
{
// Allocate a class that uses some unmanaged resource
MyDerivedResource obD = new MyDerivedResource();
// Do something with the unmanaged resource
obD.foo();
// Dispose of managed and unmanaged resources
obD.Dispose();
}
}
namespace Item18NS
{
// Cleanup design pattern for base classes
public class MyBaseResource : IDisposable
{
/* Data members */
bool bDisposed = false; // Flag for already disposed
/* Constructors/Destructors */
// Use C# destructor for finalization code. This destructor will only run if
// Dispose() was not called (Dispose if called, calls GC.SuppressFinalize)
~MyBaseResource()
{
Trace.WriteLine("MyBaseResource.~MyBaseResource");
Dispose(false);
}
/* Public interface */
public void DoSomething()
{
Trace.WriteLine("MyBaseResource.DoSomething");
}
/* Interface methods */
// IDisposable.Dispose() implementation. This method should not be made override to
// prevent derived classes from overriding
public void Dispose()
{
Trace.WriteLine("MyBaseResource.Dispose()");
// delegate to another method
Dispose(true);
// Then remove this object form the finalization queue
GC.SuppressFinalize(this);
}
/* Implementation details */
// Performs actual clean up. If bDisposing is true, Dispose has been called by the
// user and both managed and unmanaged resources must be freed. If bDisposing is false,
// this method has been called by the finalization code and it should release only
// unmanaged resources
protected virtual void Dispose(bool bDisposing)
{
Trace.WriteLine("MyBaseResource.Dispose(" + bDisposing + ")");
if (bDisposed)
return;
Trace.Indent();
if (bDisposing == true)
{
Trace.WriteLine("MyBaseResource: Freeing managed resources");
// Dispose all managed and unmanaged resources.
// TODO: free managed resources here
// TODO: free un-managed resources here
}
else
{
Trace.WriteLine("MyBaseResource: Freeing unmanaged resources");
// TODO: free un-managed resources here
}
bDisposed = true;
}
}
// Cleanup design pattern for derived classes. Note that this class does not have
// a Finalize method (destructor) because the base class's Finalize method will call
// the derived class's Dispose(bDisposing). This class does not also have an
// IDisposable.Dispose implementation because it inherits them from the base class
public class MyDerivedResource : MyBaseResource
{
/* Data members */
bool bDisposed = false;
/* Public interface */
public void foo()
{
Trace.WriteLine("MyDerivedResource.foo()");
// Allocates an unmanaged resource
}
/* Inherited implementation detail */
protected override void Dispose(bool bDisposing)
{
Trace.WriteLine("MyDerivedResource.Dispose(" + bDisposing + ")");
if (bDisposed)
return;
Trace.Indent();
try
{
if (bDisposing == true)
{
Trace.WriteLine("MyDerivedResource: Freeing managed resources");
// Dispose all managed and unmanaged resources.
// TODO: free managed resources here
// TODO: free un-managed resources here
}
else
{
Trace.WriteLine("MyDerivedResource: Freeing unmanaged resources");
// TODO: free un-managed resources here
}
bDisposed = true;
}
finally
{
// Now call the base class Dispose method
base.Dispose(bDisposing);
}
}
}
/// <summary>
/// Most important recommendation when implementing IDisposable: look very carefully
/// at any code in a finalizer and, by extension, both Dispose methods. If that code
/// is doing anything other than releasing resources, look again. Those actions likely
/// will cause bugs in your program in the future. Remove those actions, and make sure
/// that finalizers and Dispose() methods release resources and do nothing else.
/// </summary>
public class BadClass
{
// Store a reference to a global object:
private readonly ArrayList _finalizedList;
private string _msg;
public BadClass(ArrayList badList, string msg)
{
// cache the reference:
_finalizedList = badList;
_msg = (string)msg.Clone();
}
~BadClass()
{
// Add this object to the list. This object is reachable, no longer garbage.
// It's Back!
_finalizedList.Add(this);
}
}
}
Recall that abstract base classes provide a common ancestor for a class
hierarchy.
An interface describes one atomic piece of functionality that can be implemented
by
a type. A type that implements an interface must supply an implementation for
expected
methods. Abstract base classes provide a common abstraction for a set of related
types.
Also recall that public inheritance means 'is a', while interface
inheritance means 'behaves like'. In other words, base classes describe what an
object is; interfaces describe one way in which it behaves.
Abstract base classes can supply some implementation for derived types, in
addition
to describing the common behavior. You can specify data members, concrete
methods,
implementation for virtual methods, properties, events, and indexers. A base
class
can provide implementation for some of the methods, thereby providing common
implementation reuse. Any of the elements can be virtual,
abstract, or nonvirtual.
An abstract base class can provide an implementation for any concrete behavior;
interfaces cannot. If you add a method to the base class, all derived classes
are
automatically and implicitly enhanced. In that sense, base classes provide a way
to
extend the behavior of several types efficiently over time: By adding and
implementing
functionality in the base class, all derived classes immediately incorporate
that
behavior. Adding a member to an interface breaks all the classes that implement
that
interface. They will not contain the new method and will no longer compile. Each
implementer must update that type to include the new member.
Choosing between an abstract base class and an interface is a question of how
best to
support your abstractions over time. Interfaces are fixed: You release an
interface
as a contract for a set of functionality that any type can implement. Base
classes
can be extended over time. Those extensions become part of every derived class.
An interface can be implemented by any number of unrelated types. Coding to
interfaces
provides greater flexibility to other developers than coding to base class
types.
That's important because of the single inheritance hierarchy that the .NET
environment
enforces.
These two methods perform the same task:
public void PrintCollection( IEnumerable<int> collection )
{
foreach( int n
in collection )
Console.WriteLine( "Collection contains {0}", n.ToString( ) );
}
public void PrintCollection( MyCollection<int> collection )
{
foreach( int n
in collection )
Console.WriteLine( "Collection contains {0}", n.ToString( ) );
}
Coding the method using interfaces as its parameter types is far more generic
and
far easier to reuse. The second method is far less reusable. It cannot be used
with List<T> and many other collection classes.
When your type exposes properties as class types, it exposes the entire
interface
to that class. Using interfaces, you can choose to expose only the methods and
properties you want clients to use. The class used to implement the interface is
an implementation detail that can change over time (see Item 23
Avoid Returning References to Internal Class Objects).
Furthermore, unrelated types can implement the same interface. Suppose you're
building
an application that manages employees, customers, and vendors. Those are
unrelated,
at least in terms of the class hierarchy. But they share some common
functionality.
They all have names, and you will likely display those names in Windows controls
in
your applications. See TestInterfaces() and more importantly,
PrintContactInfo() methods below.
To summarize, base classes describe and implement common behaviors across
related
concrete types. Interfaces describe atomic pieces of functionality that
unrelated
concrete types can implement. Both have their place. Classes define the types
you
create. Interfaces describe the behavior of those types as pieces of
functionality.
Use class hierarchies to define related types. Expose functionality using
interfaces
implemented across those types.
class Item19
{
/// <summary>
/// In Item19NS_Bad namespace, the Employee, Customer, and Vendor classes do not
/// share a common base class because they are unrelated. But they do share some
/// properties. In Item19NS_Good namespace, these properties are factored out into
/// an IContactInfo interface. This new interface simplifies programming tasks by
/// letting you build common routines for unrelated types such as
/// PrintContactInfo( IContactInfo) function below.
/// </summary>
public void TestInterfaces()
{
// Create a few unrelated types
Item19NS_Good.Customer c = new Item19NS_Good.Customer() { FirstName = "A", LastName = "B" };
Item19NS_Good.Employee e = new Item19NS_Good.Employee() { FirstName = "C", LastName = "D" };
Item19NS_Good.Vendor v = new Item19NS_Good.Vendor() { FirstName = "E", LastName = "F" };
// Because these unrelated types implement the same interface, we can
// pass the interface as a parameter to a function that know how to
// process that interface.
PrintContactInfo(c as Item19NS_Good.IContactInfo); // Output: FirstName: A. LastName: B
PrintContactInfo(e as Item19NS_Good.IContactInfo); // Output: FirstName: C. LastName: D
PrintContactInfo(v as Item19NS_Good.IContactInfo); // Output: FirstName: E. LastName: F
}
/// <summary>
/// PrintContactInfo works for all entities that implement the IContactInfo interface.
/// Customer, Employee, and Vendor all use the same routine—but only because we
/// factored them into interfaces
/// </summary>
private void PrintContactInfo( Item19NS_Good.IContactInfo info)
{
Trace.WriteLine("FirstName: " + info.FirstName + ". LastName: " + info.LastName);
}
}
namespace Item19NS_Bad
{
class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Venddor
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
namespace Item19NS_Good
{
interface IContactInfo
{
string FirstName { get; set; }
string LastName { get; set; }
}
class Customer : IContactInfo
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Employee : IContactInfo
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Vendor : IContactInfo
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
At first glance, implementing an interface seems to be the same as overriding a
virtual function. You provide a definition for a member that has been declared
in another type. That first glance is very deceiving. Implementing an
interface is very different from overriding a virtual function.
Derived classes cannot override an interface member implemented in a base class.
Interfaces can be explicitly implemented, which hides them from a class's public
interface. They are different concepts with different uses. But you can
implement
interfaces in such a manner that derived classes can modify your implementation.
You just have to create hooks for derived classes.
TestImg() method illustrates the fact that interface methods are not virtual.
But you often want to create interfaces, implement them in base classes, and
modify the behavior in derived classes. You can. You've got two options. If you
do not have access to the base class, you can re-implement the interface in the
derived class. See class MyDerivedClass2 and method
TestImg2() below.
The only way to fix this problem as shown in namespace
Item20NS2 is to modify the base class, declaring that the interface methods
should be virtual.
MyDerivedClass and all classes derived from MyClass can declare their own methods for
Message().
The overridden version will be called every time: through the
MyDerivedClass
reference, through the IMsg reference, and through the
MyClass reference.
Implementing interfaces allows more options than creating and overriding virtual
functions. You can create sealed implementations, virtual implementations, or
abstract contracts for class hierarchies. You can decide exactly how and when
derived classes can modify the default behavior for members of any interface
your class implements. Interface methods are not virtual methods, but a separate
contract.
class Item20
{
/// <summary>
/// Illustrates the fact that interface methods are not virtual.
/// </summary>
public void TestImsg()
{
// Create a derived class that has a new Message method which does
// not override MyClass.Message, but rather hides it
MyDerivedClass d = new MyDerivedClass();
d.Message(); // Prints: MyDerivedClass.Message
// Access the actual IMsg implemenation which is in the base class
IMsg imsg = d as IMsg;
imsg.Message(); // Prints: MyClass.Message
MyClass b = d as MyClass;
b.Message(); // Prints: MyClass.Message
}
public void TestImsg2()
{
// Create a derived class re-implements IMsg
MyDerivedClass2 d = new MyDerivedClass2();
d.Message(); // Prints: MyDerivedClass.Message
// Access the actual IMsg implemenation which is now in the derived class
// and not in the base calss
IMsg imsg = d as IMsg;
imsg.Message(); // Prints: MyDerivedClass.Message
MyClass b = d as MyClass;
b.Message(); // Prints: MyClass.Message
}
public void TestImsg3()
{
// Create a derived class that has a new Message method which does
// not override MyClass.Message, but rather hides it
Item20NS2.MyDerivedClass d = new Item20NS2.MyDerivedClass();
d.Message(); // Prints: MyDerivedClass.Message
// Access the actual IMsg implemenation which is in the base class
Item20NS2.IMsg imsg = d as Item20NS2.IMsg;
imsg.Message(); // Prints: MyDerivedClass.Message
Item20NS2.MyClass b = d as Item20NS2.MyClass;
b.Message(); // Prints: MyDerivedClass.Message
}
}
namespace Item20NS
{
// A Basic interface
interface IMsg
{
void Message();
}
// MyClass implements IMsg
public class MyClass : IMsg
{
public void Message()
{
Trace.WriteLine("MyClass.Message");
}
}
// A derived class that derives from MyClass. Notice the use of the the new keyword in
// the definition of the Message method. MyClass.Message() is not virtual. Derived classes
// cannot provide an overridden version of Message. The MyDerived class creates a new
// Message method, but that method does not override MyClass.Message; It hides it.
// Furthermore, MyClass. Message is still available through the IMsg reference
public class MyDerivedClass : MyClass
{
public new void Message()
{
Trace.WriteLine("MyDerivedClass.Message");
}
}
/// <summary>
/// A derived class that derives from MyClass and re-implements IMsg. Note that you still
/// need the new keyword on the MyDerivedClass2.Message() method.
/// </summary>
public class MyDerivedClass2 : MyClass, IMsg
{
public new void Message()
{
Trace.WriteLine("MyDerivedClass.Message");
}
}
}
// Improves on the classes in Item20NS by declaring the interface methods as virtual
namespace Item20NS2
{
// A Basic interface
interface IMsg
{
void Message();
}
// MyClass implements IMsg
public class MyClass : IMsg
{
public virtual void Message()
{
Trace.WriteLine("MyClass.Message");
}
}
public class MyDerivedClass : MyClass
{
public override void Message()
{
Trace.WriteLine("MyDerivedClass.Message");
}
}
}
Callbacks are used to provide feedback from a server to a client
asynchronously.
Callbacks are expressed using delegates in the C# language. A delegate is an
object that contains a reference to a method. That method can be either a static
method or an instance method.
Delegates provide type-safe callback definitions. Although the most common use
of delegates is events, that should not be the only time you use this language
feature. Any time you need to configure the communication between classes and
you desire less coupling than you get from interfaces, a delegate is the right
choice. . Using the delegate, you can communicate with one or many client
objects
configured at runtime.
Multicast delegates wrap all the functions that have been added to the delegate
in a single function call. Two caveats apply to this construct: It is not safe
in the face of exceptions, and the return value will be the return value of the
last function invocation.
Inside a multicast delegate invocation, each target is called in succession. The
delegate does not catch any exceptions. Therefore, any exception that the target
throws ends the delegate invocation chain.
You address both issues by invoking each delegate target yourself. Each delegate
you create contains a list of delegates. To examine the chain yourself and call
each one, iterate the invocation list yourself. See TestDelegates() method
below.
Note that if we did not iterate the invocation list, then the value returned from invoking the delegate is the return value from the last function in the multicast chain. All other return values are ignored
class Item21
{
private delegate bool ContinueProcessing(string strFileName);
public void TestDelegates()
{
// Create list of file to encrypt and transmit via FTP
List<string> lstFileNames = new List<string>() { "X.txt", "Y.txt", "Z.txt" };
// Create a multi-cast delegate that will be used as a callback to check
// processing on a file
ContinueProcessing cp = new ContinueProcessing(CheckEncryptionStatus);
cp += new ContinueProcessing(CheckValidContentStatus);
// Prcoess files
ProcessFiles(lstFileNames, cp);
}
/// <summary>
/// Output:
///
/// Performing checks on file X.txt
/// CheckEncryptionStatus for X.txt
/// CheckTransmitStatus for X.txt
/// X.txt passes both checks. Processing...
/// Performing checks on file Y.txt
/// CheckEncryptionStatus for Y.txt
/// CheckTransmitStatus for Y.txt
/// Y.txt does not pass checks
/// Performing checks on file Z.txt
/// CheckEncryptionStatus for Z.txt
/// CheckTransmitStatus for Z.txt
/// Z.txt does not pass checks
///
/// </summary>
/// <param name="lstFiles"></param>
/// <param name="cpDelegates"></param>
private void ProcessFiles(List<string> lstFiles, ContinueProcessing cpDelegates)
{
bool bContinue = true;
foreach (string file in lstFiles)
{
Trace.WriteLine("Performing checks on file " + file);
// Check encryption and content before processing this file further
foreach (ContinueProcessing cp in cpDelegates.GetInvocationList())
{
bContinue &= cp(file);
}
if (!bContinue)
{
Trace.WriteLine(file + " does not pass checks ");
continue;
}
// Clear to further process this file ...
Trace.WriteLine(file + " passes both checks. Processing..." );
}
}
/// <summary>
/// Callback method to check encryption status for a file
/// </summary>
private bool CheckEncryptionStatus(string file)
{
// Dummy processing
Trace.WriteLine("CheckEncryptionStatus for " + file);
if (file == "Y.txt")
return false;
else
return true;
}
/// <summary>
/// Callback method to check content status for a file
/// </summary>
/// <param name="file"></param>
private bool CheckValidContentStatus(string file)
{
// Dummy processing
Trace.WriteLine("CheckTransmitStatus for " + file);
if (file == "Z.txt")
return false;
else
return true;
}
}
Events define the outgoing interface for your type. Events are built on
delegates
to provide type-safe function signatures for event handlers. You should
use
events
when your type must communicate with multiple clients to inform them of actions
in
the system. Contrast this with callbacks (i.e., delegates) that are used to
provide
feedback from a server to a client asynchronously.
See Logger class below for an example of how to properly design an event
class
You define outgoing interfaces in classes with events: Any number of clients can
attach handlers to the events and process them. Those clients need not be known
at
compile time. Events don't need subscribers for the system to function properly.
Using events in C# decouples the sender and the possible receivers of
notifications.
The sender can be developed completely independently of any receivers. Events
are
the standard way to broadcast information about actions that your type has
taken.
namespace Item22NS
{
public class LoggerEventArgs : EventArgs
{
public readonly string Message;
public readonly int Priority;
public LoggerEventArgs(int p, string m)
{
Priority = p;
Message = m;
}
}
public class Logger
{
private static readonly Logger _instance = null;
static Logger()
{
_instance = new Logger();
}
// Private constructor. This class cannot be instantiated
private Logger() { /* Empty implementation */ }
public Logger Singleton
{
get { return _instance; }
}
// Define the event
public event EventHandler<LoggerEventArgs> Log;
// add a message, and log it.
public void AddMsg(int priority, string msg)
{
OnAddMsg(new LoggerEventArgs(priority, msg));
}
protected virtual void OnAddMsg(LoggerEventArgs args)
{
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler<LoggerEventArgs> handler = Log;
// Event will be null if there are no subscribers
if (handler != null)
handler(this, args);
}
}
}
A read-only property means that it is read-only and callers cannot modify it. Unfortunately, that's not always the way it works. If you create a property that returns a reference type, the caller can access any public member of that object, including those that modify the state of the property. For example:
public class MyBusinessObject
{
// Read Only property providing access to a private data member:
public MyReferenceType InternalData {
get; }
...
}
MyReferenceType t = bizObj.Data;
// Access object's internal data
t.SomeImportantInternalList.Clear();
// Not intended, but allowed:
Clearly, you want to prevent this kind of behavior. You do not want users to access or modify the internal state of your objects without your knowledge. You've got four different strategies for protecting your internal data structures from unintended modifications: value types, immutable types, interfaces, and wrappers
Value types are copied when clients access them through a property. Any changes to the copy retrieved by the clients of your class do not affect your object's internal state. Clients can change the copy as much as necessary to achieve their purpose.
Immutable types, such as System.String, are also safe. You can return strings, or any immutable type, safely knowing that no client of your class can modify the string. Your internal state is safe.
Define interfaces that allow clients to access a subset of your internal member's functionality (see Item 19 Prefer Defining and Implementing Interfaces to Inheritance). When you create your own classes, you can create sets of interfaces that support subsets of the functionality of your class. By exposing the functionality through those interfaces, you minimize the possibility that your internal data changes in ways you did not intend. Clients can access the internal object through the interface you supplied, which will not include the full functionality of the class.
With wrapper objects, you can provide access to internal data but prevent the
use
of mutator methods. The following uses a ReadOnlyCollection<T> to expose an
internal
list rather then exposing the underlying List<T>:
class Employees
{
private List<Employee> lst =
new List<Employee>();
// Return a read only view of the underlying collection
private ReadOnlyCollection<Employee> EmployeeView
{
get {return new ReadOnlyCollection<Employee>(lst);
}
}
However, how do respond to changes in your data when you allow public clients to
modify
it? The general technique is to use the Observer pattern: Your class
subscribes to events generated by your internal data structure. Event handlers
validate changes or respond to those changes by updating other internal state.
In summary, exposing reference types through your public interface allows users
of your
object to modify its internals without going through the methods and properties
you've
defined. You need to modify your class's interfaces to take into account that
you are
exporting references rather than values. If you simply return internal data,
you've
given access to those contained members. Your clients can call any method that
is
available in your members. You limit that access by exposing private internal
data
using interfaces, or wrapper objects. When you do want your clients to modify
your
internal data elements, you should implement the Observer pattern so that your
objects
can validate changes or respond to them.
Declarative programming can often be a simpler, more concise way to describe
the behavior of a software program than imperative programming. Declarative
programming means that the behavior of your program is defined using
declarations instead of by writing instructions. You practice declarative
programming using attributes in C#. You attach attributes to classes,
properties,
data members, or methods, and the .NET runtime adds behavior for you. This
declarative approach is simpler to implement and easier to read and maintain.
If the predefined attributes don't fit your needs, you can create your own
declarative programming constructs by defining custom attributes and using
reflection. As an example, you can create an attribute and associated code to
let users create types that define the default sort order using an attribute.
See method TestSortingViaAttributes()
below.
You could write different (and slightly simpler) versions of the sort algorithm
for every type you created. The advantage to using declarative programming is
that you can write one generic class and let a simple declaration create the
behavior for each type. The key is that the behavior changes based on a single
declaration, not based on any algorithm changes. The GenericComparer
(defined below) works for
any type decorated with the DefaultSort attribute
(defined below). If you need sorting
functionality
only once or twice in your application, write the simpler routines. However, if
you might need the same behavior for many tens of different types in your
program,
the generic algorithm and the declarative solution will save you time and energy
in the long run.
Declarative programming is a powerful tool. When you can use attributes to
declare
your intent, you save the possibility of logic mistakes in multiple similar
hand-coded algorithms. Declarative programming creates more readable, cleaner
code.
If you can use an attribute defined in the .NET Framework, do so. If not,
consider
the option of creating your own attribute definition so that you can use it to
create
the same behavior in the future.
class Item24
{
public void TestSortingViaAttributes()
{
// Create a list of customers
List<Customer> lstCustomers = new List<Customer>() { new Customer {Balance=100.0, Name="C"},
new Customer {Balance=200.0, Name="A"},
new Customer {Balance=300.0, Name="B"},
new Customer {Balance=400.0, Name="D"} };
// Sort the list. The GenericComparer examines the DefaultSort attribute and sorts
// lstCustomers based on the supplied property name
lstCustomers.Sort(new GenericComparer<Customer>());
// Display list contents
// C/100
// A/200
// B/300
// D/400
Trace.WriteLine("Sort ascending");
lstCustomers.ForEach( (Customer c) => Trace.WriteLine( c.ToString() ));
// Sort descending
lstCustomers.Sort(new GenericComparer<Customer>(true));
// Display list contents
// D/400
// B/300
// A/200
// C/100
Trace.WriteLine("Sort descending");
lstCustomers.ForEach( (Customer c) => Trace.WriteLine( c.ToString()) );
}
}
namespace Item24NS
{
/// <summary>
/// Defines a new attribute that indicates the field on which to sort
/// a collection of objects.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
class DefaultSortAttribute : System.Attribute
{
public string Name { get; set; }
public DefaultSortAttribute(string name)
{
Name = name;
}
}
/// <summary>
/// DefaultSort attribute defines how a collection of Customer objects should
/// be sorted
/// </summary>
[DefaultSort("Balance")]
class Customer
{
public string Name { get; set; }
public double Balance { get; set; }
public override string ToString()
{
return Name + "/" + Balance;
}
}
internal class GenericComparer<T> : IComparer<T>
{
private readonly PropertyDescriptor _sortProp; // default property information
private readonly bool _reverse = false; // Ascending or descending.
// Construct for a type
public GenericComparer() :this(false) { /* Empty implementation */ }
// Construct for a type and a direction
public GenericComparer(bool reverse)
{
_reverse = reverse;
// Get all DefaultSort attributes defined on this type
object[] a = typeof(T).GetCustomAttributes( typeof(DefaultSortAttribute), false);
// Get the PropertyDescriptor for that property:
if (a.Length > 0)
{
DefaultSortAttribute sortName = a[0] as DefaultSortAttribute;
string name = sortName.Name;
// Get list of properties defined on the current type. Search for the property
// whose name matches the name given in DefaultSort attribute
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
foreach (PropertyDescriptor p in props)
{
if (p.Name == name)
{
// Found the default sort property:
_sortProp = p;
break;
}
}
}
}
#region IComparer<T> Members
public int Compare(T left, T right)
{
// null is less than any real object:
if ((left == null) && (right == null))
return 0;
if (left == null)
return -1;
if (right == null)
return 1;
if (_sortProp == null)
{
return 0;
}
// Get the sort property from each object:
IComparable lField = _sortProp.GetValue(left) as IComparable;
IComparable rField = _sortProp.GetValue(right) as IComparable;
int rVal = 0;
if (lField == null)
if (rField == null)
return 0;
else
return -1;
rVal = lField.CompareTo(rField);
return (_reverse) ? -rVal : rVal;
}
#endregion
}
}
Persistence is a core feature of a type. It's one of those basic elements that
no one notices until you neglect to support it. If your type does not support
serialization properly, you create more work for all developers who intend to
use your types as a member or base class. It's unlikely that clients could
properly implement serialization for your types without access to private
details in your types. If you don't supply serialization, it's difficult or
impossible for users of your class to add it.
Instead, prefer adding serialization to your types when practical. It should be
practical for all types that do not represent UI widgets, windows, or forms.
.NET Serialization support is so simple that there is no reasonable excuse not
to support it. In many cases, adding [Serializable] attribute is enough:
[Serializable]
public class MyType
{
private string _label;
private int _value;
private OtherClass _object;
}
The [Serializable] attribute works here only if the
OtherClass type supports .NET
serialization. If OtherClass is not serializable, you get a runtime error and
you have to write your own code to serialize MyType and the
OtherClass object
inside it. That's just not possible without extensive knowledge of the internals
defined in OtherClass.
The [Serializable] attribute is the simplest technique to create serializable
objects. Sometimes, you do not want to serialize all the members of an object;
Some members might exist only to cache the result of a lengthy operation. Other
members might hold on to runtime resources that are needed only for in-memory
operations. Attach the [NonSerialized] attribute to any of the data members that
should not be saved as part of the object state:
[Serializable]
public class MyType
{
private string _label;
// Serialized
[NonSerialized]
private int _cachedValue;
// Not Serialized
private OtherClass _object;
// Serialized
}
[Nonserialized] members add a little more work for the class designer.
Serialization
APIs do not initialize nonserialized members during the deserialization process.
None of the types' constructors is called, so the member initializers are not
executed, either. When using the serializable attributes, nonserialized members
get
the default system-initialized value: 0 or
null. If this default initialization
is
not right, you need to implement the IDeserializationCallback interface to
initialize
these nonserializable members. IDeserializationCallback contains only one
method, OnDeserialization. The framework calls this method after the entire object graph
has
been deserialized. You use this method to initialize any nonserialized members
in
your object. Because the entire object graph has been read, you know that any
function
you might want to call on your object or any of its serialized members is safe.
Unfortunately, it's not fool-proof. After the entire object graph has been read,
the framework calls OnDeserialization on every object in the graph that supports
the IDeserializationCallback interface. Any other objects in the object graph
can
call your object's public members when processing OnDeserialization. If they go
first,
your object's nonserialized members are null, or
0. Order is not guaranteed, so
you
must ensure that all your public methods handle the case in which nonserialized
members
have not been initialized.
Full examples of serialization can be found under .NET / Programming /
Serialization chapter
The .NET Framework defines two interfaces that describe ordering relationships
for .NET types: IComparable<T> and
IComparer<T> interfaces. IComparable<T>
defines the natural order for your types. A type implements IComparer<T> to
describe alternative orderings.
Note that you should always use these generic interfaces rather than their
non-generic equivalents. With the non-generic equivalents, you have to check
the runtime type of the IComparable.CompareTo() argument. Also with the non-
generic interfaces, proper arguments must be boxed and unboxed to provide the
actual comparison. That's an extra runtime expense for each compare.
Ordering relations (IComparable<T> and
IComparer<T>) and equality (Equals() or
the operator== are distinct operations. You do not need to implement equality
comparison to have an ordering relation. In fact, reference types commonly
implement ordering based on the object contents, yet implement equality based
on object identity. CompareTo() returns
0, even though Equals() returns
false.
That's perfectly legal. Equality and ordering relations are not necessarily the
same.
IComparable<T> and IComparer<T> are the standard mechanisms for providing ordering relations for your types. IComparable<T> should be used for the most natural ordering. When you implement IComparable<T>, you should overload the comparison operators (<, >, <=, and >=) consistently with our IComparable<T> ordering. IComparer<T> can be used to provide alternative orderings or can be used when you need to provide ordering for a type that does not provide it for you.
class Item26
{
public void TestIComparable()
{
// Create Customer objects that can be sorted
Customer c1 = new Customer() { ID = 1, Name = "X" };
Customer c2 = new Customer() { ID = 2, Name = "Y" };
Customer c3 = new Customer() { ID = 3, Name = "Z" };
List<Customer> lstCustomers = new List<Customer>() { c2, c3, c1 };
// Sort customers with the default sort
// X/1
// Y/2
// Z/3
lstCustomers.Sort();
lstCustomers.ForEach( (Customer c) => Trace.WriteLine( c.ToString() ));
// Sort customers with a specialized sort
// Z/3
// Y/2
// X/1
lstCustomers.Sort(Customer.CustomerNameComparerDesc());
lstCustomers.ForEach((Customer c) => Trace.WriteLine(c.ToString()));
// Standard relational operators must be overridden before objects of type
// Customer can be compared.
bool b1 = c1 < c2;
}
}
namespace Item26NS
{
/// <summary>
/// A simple class that implements IComparable<T> interface.
///
/// While not required, this class overrides the standard relational operators.
/// Those should make use of the IComparable<T>.CompareTo() method as shown below.
/// </summary>
struct Customer : IComparable<Customer>
{
public string Name { get; set; }
public int ID { get; set; }
public override string ToString()
{
return Name + "/" + ID.ToString();
}
#region IComparable<Customer> Members
public int CompareTo(Customer rhs)
{
// If Customer type was a class, then the following two statements
// would be required
//if (rhs == null) return -1; // Check against a null input
//if (rhs == this) return 0; // Check against self-comparison
return ID.CompareTo(rhs.ID);
}
#endregion
#region IComparer<Customer> Members
private class CustomerNameAsc : IComparer<Customer>
{
#region IComparer<Customer> Members
public int Compare(Customer lhs, Customer rhs)
{
return lhs.CompareTo(rhs);
}
#endregion
}
private class CustomerNameDesc : IComparer<Customer>
{
#region IComparer<Customer> Members
public int Compare(Customer lhs, Customer rhs)
{
return lhs.CompareTo(rhs) * -1; // -1 to reverse comparison
}
#endregion
}
public static IComparer<Customer> CustomerNameComparerAsc()
{
return new Customer.CustomerNameAsc();
}
public static IComparer<Customer> CustomerNameComparerDesc()
{
return new Customer.CustomerNameDesc();
}
#endregion
// If operator < is implemented, then operator > must also be implemented.
// Otherwise, error CS0216 is generated: The operator
// 'Customer.operator <(Customer, Customer)' requires a matching operator '>'
// to also be defined
public static bool operator <(Customer lhs, Customer rhs)
{
return lhs.CompareTo(rhs) < 0;
}
public static bool operator >(Customer lhs, Customer rhs)
{
return lhs.CompareTo(rhs) > 0;
}
public static bool operator <= (Customer lhs, Customer rhs)
{
return lhs.CompareTo( rhs) <= 0;
}
public static bool operator >=(Customer lhs, Customer rhs)
{
return lhs.CompareTo(rhs) >= 0;
}
}
}
ICloneable sounds like a good idea: You implement the
ICloneable interface for
types that support copies. If you don't want to support copies, don't implement
it. But your type does not live in a vacuum. Your decision to support
ICloneable
affects derived types as well. Once a type supports ICloneable, all its derived
types must do the same. All its member types must also support
ICloneable or
have
some other mechanism to create a copy. Finally, supporting deep copies can be
very
problematic when you create designs that contain webs of objects.
ICloneable
finesses this problem in its official definition: It supports either a deep or a
shallow copy. A shallow copy creates a new object that contains copies of all
member
variables. If those member variables are reference types, the new object refers
to
the same object that the original does. A deep copy creates a new object that
copies
all member variables as well. All reference types are cloned recursively in the
copy.
In built-in types, such as integers, the deep and shallow copies produce the
same
results. Which one does a type support? That depends on the type. But mixing
shallow
and deep copies in the same object causes quite a few inconsistencies. When you
go
wading into the ICloneable waters, it can be hard to escape. Most often,
avoiding ICloneable altogether makes a simpler class. It's easier to use, and it's easier
to
implement.
Any value type that contains only built-in types as members does not need to
support ICloneable; a simple assignment copies all the values of the
struct more
efficiently
than Clone(). The Clone()
method must box its return so that it can be coerced into an
Object
reference. The caller must perform another cast to extract the value from the
box.
You've got enough to do. Don't write a Clone() function that replicates assignment.
What about value types that contain reference types? The most obvious case is a
value
type that contains a string:
public struct ErrorMessage
{
private int errCode;
private int details;
private string msg;
}
string is a special case because this class is immutable. If you assign an error
message object, both error message objects refer to the same string. This does
not
cause any of the problems that might happen with a general reference type. If
you
change the msg variable through either reference, you create a new
string
object.
Therefore, do not implement ICloneable on a value
type (struct) even if it contains strings.
The general case of creating a struct that contains arbitrary reference
variables is more complicated. It's also far more rare. The built-in
assignment for the struct creates a shallow copy of
the container reference variable, with both structs referring to the same object. To create a deep copy, you need to clone the
contained
reference type, and you need to know that the reference type supported a deep
copy
with its Clone() method. In either way, you don't add support for
ICloneable to
a
value type; the assignment operator creates a new copy of any value type.
Therefore, Do not implement ICloneable
on a value type (struct) that contains arbitrary reference variables.
Reference types should support the ICloneable interface to indicate that they
support
either shallow or deep copying. You should add support for ICloneable
judiciously; When you implement ICloneable, you
force all derived classes to implement ICloneable as
well. See TestICloneable1() method below.
When an entire hierarchy must implement ICloneable, create an abstract
Clone()
method
and force all derived classes to implement it. In those cases, you define a
protected
copy constructors on the base to allow the derived class to create copies of the
base
members. See method TestICloneable2() below.
ICloneable does have its use, but it is the exception rather than rule. You
should
never add support for ICloneable to value types; use the assignment operation
instead.
You should add support for ICloneable to leaf classes when a copy operation is
truly
necessary for the type. Base classes that are likely to be used where
ICloneable
will
be supported should create a protected copy constructor. In all other cases,
avoid ICloneable.
class Item27
{
/// <summary>
/// Attempts to clone a derived object that does not implement ICloneable but whose
/// base does implement ICloneable. In the code below, dCloned is NULL. MyDerived
/// class does inherit ICloneable.Clone() from MyBase, but that implementation is
/// not correct for MyDerived: It only clones the base type. MyBase.Clone() creates
/// a MyBase object, not a MyDerived object. Therefore, when you implement ICloneable,
/// you force all derived classes to implement it as well. To support cloning, derived
/// classes can add only member variables that are value types or reference types that
/// implement ICloneable.
/// </summary>
public void TestICloneable1()
{
// Create and initiailze a MyDerived object
MyDerived d = new MyDerived();
d.Name = "X";
d.IDs = new int[] { 1, 2, 3, 4, 5 };
d.Salries = new double[] { 100.0, 200.0, 300.0 };
// Now clone object d. d is NULL!
MyDerived dCloned = d.Clone() as MyDerived;
}
public void TestICloneable2()
{
// Create and initiailze a Derived object
Derived d = new Derived();
d.Name = "X";
d.IDs = new int[] { 1, 2, 3, 4, 5 };
d.Salries = new double[] { 100.0, 200.0, 300.0 };
// Clone object d. d is not null and cloned properly
Derived dCloned = d.Clone() as Derived;
}
}
namespace Item27NS
{
public class MyBase : ICloneable
{
public string Name { get; set; }
public int[] IDs { get; set; }
#region ICloneable Members
public object Clone()
{
// Create and initialize a clonet
MyBase clone = new MyBase();
clone.Name = this.Name;
clone.IDs = this.IDs.ToArray();
return clone;
}
#endregion
}
public class MyDerived : MyBase
{
public double[] Salries {get; set;}
}
}
namespace Item27NS2
{
class BaseType
{
public string Name { get; set; }
public int[] IDs { get; set; }
public BaseType() {/* Empty Implementation */ }
// Protected copy constructor used by derived types to clone
protected BaseType(BaseType src)
{
Name = src.Name;
IDs = src.IDs.Clone() as int[];
}
}
sealed class Derived : BaseType, ICloneable
{
public double[] Salries { get; set; }
public Derived() {/* Empty Implementation */ }
// Construct a copy using the base class copy ctor
private Derived(Derived src) : base(src)
{
Salries = src.Salries.Clone() as double[];
}
public object Clone()
{
Derived rVal = new Derived(this);
return rVal;
}
}
}
Conversion operators introduce a kind of substitutability between classes.
Substitutability means that one class can be substituted for another. When
you define a conversion operator for your type, you tell the compiler that
your type may be substituted for the target type. These substitutions often
result in subtle errors because your type probably isn't a perfect substitute
for the target type.
If you want to convert another type into your type, use a constructor. This
more clearly reflects the action of creating a new object. Conversion operators
can introduce hard-to-find problems in your code.
Assume you have a hierarchy where Circle,
Ellipse and Square derive from a
Shape
base class. However, you realize that every Circle could be an
Ellipse. In
addition, some ellipses could be substituted for circles. That leads you to add
two conversion operators. Every Circle is an
Ellipse, so you add an implicit
conversion to create a new Ellipse from a
Circle. An implicit conversion
operator will be called whenever one type needs to be converted to another type.
By contrast, an explicit conversion will be called only when the programmer puts
a cast operator in the source code.
public class Circle : Shape
{
private PointF _center;
private float _radius;
public Circle(PointF c,
float r)
{
_center = c;
_radius = r;
}
static public implicit operator Ellipse(Circle c)
{
return new Ellipse(c._center, c._center, c._radius, c._radius);
}
}
Conversion from Circle to Ellipse happens automatically in the following
function:
public double ComputeArea( Ellipse e)
{
// Compute and return area of the ellipse
}
// call it:
Circle c = new Circle( new PointF( 3.0f, 0 ), 5.0f );
ComputeArea( c );
This sample shows substitutability: A Circle
object has been substituted for an
Ellipse object.
The ComputeArea function above works even with the substitution. But examine this
function:
public void Flatten( Ellipse e )
{
e.R1 /= 2;
e.R2 *= 2;
}
// call it using a circle:
Circle c = new Circle( new PointF ( 3.0f, 0 ), 5.0f );
Flatten( c );
This won't work. The Flatten() method takes an ellipse as an argument. The
compiler
convert a Circle to an Ellipse. You've created an implicit conversion that does
that.
Your conversion gets called, and the Flatten() function receives as its
parameter
the Ellipse created by your implicit conversion. This temporary object is
modified by
the Flatten() function and immediately becomes garbage. The side effects
expected from
your Flatten() function occur, but only on a temporary object. The end result is
that
nothing happens to the Circle object,
c.
Changing the conversion from implicit to explicit only forces users to add a
cast to
the call:
Circle c = new Circle(
new PointF( 3.0f, 0 ), 5.0f );
Flatten( ( Ellipse ) c );
The original problem remains. You just forced your users to add a cast to cause
the
problem. You still create a temporary object, flatten the temporary object, and
throw
it away. The Circle object c is not modified at all. Instead, if you create a
constructor
to convert the Circle to an Ellipse, the actions are clearer:
Circle c = new Circle(
new PointF( 3.0f, 0 ), 5.0f );
Flatten ( new Ellipse( c ));
Most programmers would see the previous two lines and immediately realize that
any
modifications to the Ellipse object passed to
Flatten() are lost. They would fix the
problem
by keeping track of the new object:
Circle c = new Circle(
new PointF( 3.0f, 0 ), 5.0f );
//Work with the circle.
// ...
// Convert to an ellipse.
Ellipse e = new Ellipse( c );
Flatten( e );
Conversion operators introduce a form of substitutability that causes problems
in your code. You're indicating that, in all cases, users can reasonably expect
that another class can be used in place of the one you created. When this
substituted object is accessed, you cause clients to work with temporary objects
or internal fields in place of the class you created. You then modify temporary
objects and discard the results. These subtle bugs are hard to find because the
compiler generates code to convert these objects. Avoid conversion operators.
You use the new modifier on a class member to redefine a nonvirtual member
inherited from a base class. Redefining nonvirtual methods creates ambiguous
behavior.
Consider the following block of code:
object c = MakeObject();
// Call through MyClass reference:
MyClass cl = c as MyClass;
cl.MagicMethod( );
// Call through MyOtherClass reference:
MyOtherClass cl2 = c as MyOtherClass;
cl2.MagicMethod( );
When the new modifier is involved, that just isn't the case:
public class MyClass
{
public void MagicMethod( )
{
// ...
}
}
public class MyOtherClass : MyClass
{
// Redefine MagicMethod for this class.
public new void MagicMethod( )
{
// ...
}
}
If you call the same function on the same object, you expect the same code
to execute. The fact that changing the reference, the label, that you use to
call the function changes the behavior feels very wrong. It's inconsistent.
A MyOtherClass object behaves differently in response to how you refer to
it. The new modifier does not make a nonvirtual method into a virtual method
after the fact. Instead, it lets you add a different method in your class's
naming scope.
There is one time, and one time only, when you want to use the
new modifier: You add new
to incorporate a new version of a base class that contains a method name that
you already use. The new modifier handles the case in which an upgrade
to a base class now collides with a member that you previously declared in your
derived class.
The new modifier must be used with caution. If you apply it indiscriminately,
you create ambiguous method calls in your objects. It's for the special case
in which upgrades in your base class cause collisions in your derived class.
Even in that situation, think carefully before using it. Most importantly,
don't use it in any other situations.
The .NET environment is language agnostic: Developers can
incorporate components written in different .NET languages without limitations.
In practice, it's almost true. You must create assemblies that are compliant
with the Common Language Subsystem (CLS) to guarantee that developers writing
programs in other languages can use your components.
To create a CLS-compliant assembly, you must create an assembly whose public
interface is limited to those features in the CLS specification. Then any
language supporting the CLS specification must be capable of using the
component.
To create a CLS-compliant assembly, you must follow two rules. First, the type
of all parameters and return values from public and protected members must be
CLS compliant. Second, any non-CLS-compliant public
or protected member must have a CLS-compliant
synonym.
The first rule is simple to follow: You can have it enforced by the compiler.
Add the [CLSCompliant] attribute to your assembly:
[assembly: CLSCompliant(true)]
After turning on CLS compliance, these two definitions won't compile because
unsigned integers are not compliant with CLS:
//Not CLS Compliant, returns unsigned
int:
public UInt32 Foo( )
{
return _foo;
}
// Not CLS compliant,
parameter is an unsigned int.
public void Foo2( UInt32 parm )
{
}
Remember that creating a CLS-compliant assembly affects only items that can be
seen outside of the current assembly. Foo and
Foo2 generate CLS compliance errors when declared
either public or protected. However, if Foo and
Foo2 were internal, or private, they could be
included in a CLS-compliant assembly.
Sometimes what worked in our previous environment is
counterproductive in the .NET environment. This is very true when you try to
hand-optimize algorithms for the C# compiler. Your actions often prevent the JIT
compiler from more effective optimizations. You're better off writing the
clearest code you can create. Let the JIT compiler do the rest. One of the most
common examples of premature optimizations causing problems is when you create
longer and more complicated functions in the hopes of avoiding function calls.
The .NET runtime invokes the JIT compiler to translate the IL generated by the
C# compiler into machine code. The CLR invokes the JITer on a
function-by-function basis. Functions that do not ever get called do not get
JITed. You can minimize the amount of extraneous code that gets JITed by
factoring code into more, smaller functions rather than fewer larger functions.
Smaller and simpler functions make it easier for the JIT compiler to support
enregistration. Enregistration is the process of selecting which local variables
can be stored in registers rather than on the stack. Creating fewer local
variables gives the JIT compiler a better chance to find the best candidates for
enregistration.
The JIT compiler also makes decisions about inlining methods. Inlining means to
substitute the body of a function for the function call. But remember that even
small functions that are virtual or that contain try/catch blocks cannot be
inlined.
Remember that translating your C# code into machine-executable code is a
two-step process. The C# compiler generates IL that gets delivered in
assemblies. The JIT compiler generates machine code for each method (or group of
methods, when inlining is involved), as needed. Small functions make it much
easier for the JIT compiler to amortize that cost. Small functions are also more
likely to be candidates for inlining.
Smaller and cohesive assemblies means that you should build
assemblies that are the right size and contain a small number of public types.
Many developers put everything but the kitchen sink in one assembly. That makes
it hard to reuse components and harder to update parts of a system. Many smaller
assemblies make it easier to use your classes as binary components.
Cohesion is the degree to which the responsibilities of a single component form
a meaningful unit. Cohesive components can be described in a single simple
sentence. For example, the System.Collections
assembly 'provides data structures for storing sets of related objects' and the
System.Windows.Forms assembly 'provides classes that
model Windows controls.' You should be able to describe your own assemblies in
the same fashion using one simple sentence.
Your goal is to create the best-sized package for the functionality you are
delivering in your component. This goal is easier to achieve with cohesive
components: Each component should have one responsibility.
Smaller assemblies also let you amortize the cost of application startup. The
larger an assembly is, the more work the CPU does to load the assembly and
convert the necessary IL into machine instructions. Only the routines called at
startup are JITed, but the entire assembly gets loaded and the CLR creates stubs
for every method in the assembly.
Not every type you create needs to be public. You should give each
type the least visibility necessary to accomplish your purpose. Expose only what
needs to be exposed. Try implementing public interfaces with less visible
classes.
Exposing your functionality using interfaces enables you to more easily create
internal classes without limiting their usefulness outside of the assembly (see
Item 19
Prefer Defining and Implementing Interfaces to Inheritance). Does the
type need to be public, or is an aggregation of interfaces a better way to
describe its functionality? Internal classes allow you to replace the class with
a different version, as long as it implements the same interfaces. As an
example, consider Item33NS_Bad.PhoneValidator
class that validates phone numbers. Assume now you need to use an international
version in one installation. Rather than stick the extra functionality in this
one class, you're better off reducing the coupling between the different items.
You create an interface to validate any phone number, and you change the
existing phone validator to implement that interface, and make it an internal
class. See Item33NS_Good members for full example.
Outside the assembly, only the IPhoneValidator
interface is visible. The classes, which are specific for different regions in
the world, are visible only inside the assembly. You can add different
validation classes for different regions without disturbing any other assemblies
in the system. By limiting the scope of the classes, you have limited the code
you need to change to update and extend the entire system.
class Item33
{
public void TestPublicTypeVisiblilty()
{
// Bad style
Item33NS_Bad.PhoneValidator pv = new Item33NS_Bad.PhoneValidator();
pv.ValidateNumber("0919 555 66666");
// Good style
IPhoneValidator ipv = PhoneConverterFactory.CreatePhoneConverter(EnumPhoneConverter.US);
ipv.ValidateNumber("0044 0207 555 6666");
}
}
namespace Item33NS_Bad
{
public class PhoneValidator
{
public bool ValidateNumber(string no)
{
// perform validation. Check for valid area code, exchange.
return true;
}
}
}
namespace Item33NS_Good
{
public interface IPhoneValidator
{
bool ValidateNumber(string no);
}
// Note use of 'internal' access modifier
internal class USPhoneValidator : IPhoneValidator
{
public bool ValidateNumber(string no)
{
// perform validation. Check for valid area code, exchange.
return true;
}
}
// Note use of 'internal' access modifier
internal class InternationalPhoneValidator : IPhoneValidator
{
public bool ValidateNumber(string no)
{
// perform validation. Check international code.
return true;
}
}
public enum EnumPhoneConverter { US, INTL }
// Factory class to create proper phone validator
public class PhoneConverterFactory
{
public static IPhoneValidator CreatePhoneConverter(EnumPhoneConverter ePC )
{
switch (ePC)
{
case EnumPhoneConverter.INTL:
return new InternationalPhoneValidator() as IPhoneValidator;
case EnumPhoneConverter.US:
return new USPhoneValidator() as IPhoneValidator;
default:
throw new InvalidOperationException("Invalid option");
}
}
}
}
The cost and inconvenience of a communication protocol dictates how you should
use the medium. Whether you use WCF or a web service, you must remember that the
most expensive part of the operation comes when you transfer objects between
distant machines. You must stop creating remote APIs that are simply a
repackaging of the same local interfaces that you use. It works, but it reeks of
inefficiency. Your application waits for the network each time you make a round
trip to pass a new piece of information through the pipe. The more granular the
API is, the higher percentage of time your application spends waiting for data
to return from the server.
Instead, create WCF interfaces based on serializing documents or sets of objects
between client and server. Your remote communications should work like the order
form you fax to the catalog company: The client machine should be capable of
working for extended periods of time without contacting the server. Then, when
all the information to complete the transaction is filled in, the client can
send the entire document to the server. The server's responses work the same
way: When information gets sent from the server to the client, the client
receives all the information necessary to complete all the tasks at hand.
For example, in an order processing system, you may have a Customer class to
represent customers. When it comes to client-server interactions, you would
create a local Customer object and transfer the Customer to the server after all
the fields have been set:
Customer customer = new
Customer () { Name="X", ShippingAddress="Some Address" };
proxyServer.AddCustomer( customer );
The customer example illustrates an obvious and simple example: transfer entire
objects back and forth between client and server. But to write efficient
programs, you need to extend that simple example to include the right set of
related objects. Making remote invocations to set a single property of an object
is too small of a granularity. But one customer might not be the right
granularity for transactions between the client and server, either.
Imagine that it is a major catalog ordering house with 1 Million customers, and
that each customer has, on average, 15 orders in the last year. Your design task
is to determine the most efficient set of objects to transfer between client
machines and the server.
You can begin by eliminating some obvious choices. Retrieving every customer and
every order is clearly prohibitive: 1 million customers and 15 million order
records are just too much data to bring to each client. The obvious choice is to
retrieve one customer, with all orders that have been placed by that customer.
The server method would be something like this:
public OrderData FindOrders(
string customerName ) { ... }
Or is that right? Orders that have been shipped and received by the customer are
almost certainly not needed at the client machine. A better answer is to
retrieve only the open orders for the requested customer. The server method
would change to something like this:
public OrderData
FindOpenOrders( string customerName )
In general, client/server interactions are best optimized, by using the
following pattern:
On startup, retrieve all relevant data
When changes are done, update the server.
Server updates the database and uses a publisher/subscriber module to publish all changed data to connected clients.
The end result, is that clients only need to connect to the server on initial
startup and when they need to update data.
But how can we further limit the size of each transaction without increasing the
number of transactions or the latency of the server's response. Here you need
some statistics to help make you intelligent assumptions with respect to system
usage. For example, you track some statistics and find that if customers go six
months without ordering, they are very unlikely to order again. So you stop
retrieving those customers and their orders at the beginning of the day. That
shrinks the size of the initial transaction. You also find that any customer who
calls shortly after placing an order is usually inquiring about the last order.
So you modify the list of orders sent down to the client to include only the
last order rather than all orders.
To summarize, you want to minimize both the frequency and the size of the
transactions sent between machines. Those two goals are at odds, and you
need to make trade-offs between them. You should end up close to the center of
the two extremes, but err toward the side of fewer, larger transactions.
Many .NET classes provide two different ways to handle events from the system. You can attach an event handler, or you can override a virtual function in the base class. Why provide two ways of doing the same thing? Because different situations call for different methods. Inside derived classes, you should always override the virtual function. Limit your use of the event handlers to responding to events in unrelated objects.
The following illustrates the two approaches for handling the
MouseDown event:
public class MyForm : Form
{
protected override void
OnMouseDown(MouseEventArgs e)
{
try
{
HandleMouseDown( e );
}
catch (
Exception e1 )
{
// add specific error handling here.
}
// Almost
always call base class to let other event handlers process message.
// Users of your class expect it.
base.OnMouseDown(
e );
}
}
Or, you could attach an event handler:
public class MyForm : Form
{
public MyForm( )
{
this.MouseDown
+= new MouseEventHandler(
this.MouseDownHandler );
}
private void MouseDownHandler(
object sender, MouseEventArgs e )
{
try
{
HandleMouseDown( e );
}
catch (
Exception e1 )
{
// add specific error handling here.
}
}
}
The first method is preferred. If an event handler throws an exception, no other handlers in the chain for that event are called (see item 21 Express Callbacks with Delegates). By overriding the protected virtual function, your handler gets called first. The base class version of the virtual function is responsible for calling any event handlers attached to the particular event. That means that if you want the event handlers called (and you almost always do), you must call the base class. In some rare cases, you will want to replace the default behavior instead of calling the base class version so that none of the event handlers gets called. You can't guarantee that all the event handlers will be called because some ill-formed event handler might throw an exception, but you can guarantee that your derived class's behavior is correct.
Using the override is more efficient than attaching the event handler. Event-handling mechanism takes more work for the processor because it must examine the event to see if any event handlers have been attached. If so, it must iterate the entire invocation list. Each method in the event invocation list must be called. Determining whether there are event handlers and iterating each at runtime takes more execution time than invoking one virtual function.
As mentioned above, the overrides are for derived classes. Every other class must use the event mechanism. For example, you often add a button click handler in a form. The event is generated by the button, but the form object handles the event. You could define a custom button and override the click handler in that class, but that's too much work to handle one event. Somehow, your custom button must communicate to the form that the button was clicked. The obvious way to handle that is to create an event. It would be simpler to just attach the form's event handler to the form in the first place. That's why the .NET Framework designers put those events in the forms in the first place.
Another reason for the event mechanism is that events are wired up at runtime. You have more flexibility using events. You can wire up different event handlers, depending on the circumstances of the program. Finally, with events, you can hook up multiple event handlers to the same event. For example, in a drawing program you might have multiple event handlers hooked up on the MouseDown event. The first would perform the particular action. The second might update the status bar or update the accessibility of different commands. Multiple actions can take place in response to the same event.
Tracing is the most important debugging technique:
System.Diagnostics namespace contains two classes for tracing System.Diagnostics.Debug and System.Diagnostics.Trace. Use the first only if you want the traces to be in Debug builds. Use the second, if you want trace statements in both Release and Debug builds. When a message is generated through a call to one of the Write() methods, the message is passed to listeners registered for the current application domain. It is the responsibility of those listeners to decide what to do with those messages. When the application domain is created, an instance of the DefaultTraceListener class is added to the list of registered listeners. This list is held in the static property Debug.Listeners which allows you to manage the list of listeners. If you do not want the default listener, you can remove it and add your own. Every listener class derives from the base TraceListener class.
In addition to the default trace listener, DefaultTraceListener, the Framework provides two other listener classes, TextWriterTraceListener and EventLogTraceListener, which write to a log file and the event log, respectively:
// C#
Debug.Listeners.Add( new TextWriterTraceListener(Console.out) );
// send output to the console
An alternative to the Debug class is the Trace class. Trace has the same methods and properties as Debug, but the code is only called if you define the TRACE symbol. However, note that release code should not be compiled with TRACE defined. If you find that a process on your machine has been compiled with TRACE defined, you can turn off this facility (and route the trace messages to a log file) through a configuration file:
<configuration>
<system.diagnostics>
<assert assertuienabled="false" logfilename="asserts.log"/>
</system.diagnostics>
</configuration>
One final way of diagnosing problems is to define a global variables or switch that can turn tracing on or off. The best way to control this variable is through the configuration file. The configuration file can contain many such switches, and diagnostic code can read these swtich(es) and act according. For example, one switch might enable/disable tracing only for basic information, while another might enable/disable tracing for extensive debugging information:
// Configuraiton file
<configuration>
<system.diagnostics>
<switches>
<add name="BasicInfo" value="1"/>
<add name="DetailedInfo" value="0"/>
<switches>
</system.diagnostics>
</configuration>
// C#
public class test
{
public void f()
{
BooleanSwitch bBasic = new BooleanSwitch("BasicInfo", "Display basic
information");
BooleanSwitch bDetailed = new BooleanSwitch("DetailedInfo", "Display
detailed information");
if (bBasic.Enabled)
// Write only basic tracing information
else if(bDetailed.Enabled)
// Write detailed tracing information
else
// Do not write tracing statements
...
}
}
The System.Diagnostics namespace also contains a switch called TraceSwitch. This class has a Level property that is one of the TraceLevel enum values (Error, Warning, Verbose, Info). In your code, you create and check a TraceSwitch object to determine the amount of information to display.
The .NET Framework has extensive capabilities that you can use to
minimize the amount of code you need to write, yet still validate every piece of
data that your users give you. For Windows Forms validation you need to write an
event handler for the
System.Windows.Forms.Control.Validating event. Or, if you are creating
your own custom control, override the OnValidating
method (see Item 35 Prefer
Overrides to Event Handlers). A standard form for a validation event handler
follows:
private void
textBoxName_Validating( object sender,
System.ComponentModel.CancelEventArgs e )
{
string error = null;
//
Perform your test
if (
textBoxName.Text.Length == 0 )
{
// If the test
fails, set the error string and cancel the validation event.
error = "Please enter a name";
e.Cancel = true;
}
// Update the state of an error
provider with the correct error text. Set to null for no error.
this.errorProviderAll.SetError(
textBoxName, error );
}
You have a few more small tasks to make sure that no invalid input sneaks
through. Every control contains a CausesValidation
property. This property determines whether the control participates in
validation. In general, you should leave it true for all of your controls,
except for the Cancel button. If you forget, the
user must create valid input to cancel from your dialog box. The second small
task is to add an OK handler to force validation of
all controls. Validation happens only when a user visits and leaves a control.
If the user opens a form and immediately presses OK,
none of your validation code executes. To fix that, you add an
OK button handler to walk through all your controls
and force them to validate. The following two routines show you how to do this
correctly. The recursive routines handle those controls that are also containers
for other controls: tab pages, group boxes, and panels:
private void buttonOK_Click(
object sender, System.EventArgs e )
{
// Validate everyone: Here,
this.DialogResult will be set to DialogResult.OK
ValidateAllChildren( this
);
}
private void ValidateAllChildren( Control parent )
{
// If validation already failed, stop
checking.
if(
this.DialogResult == DialogResult.None )
return;
// For every control
foreach( Control c
in parent.Controls )
{
// Give it
focus
c.Focus( );
// Try and
validate:
if
(!this.Validate( ))
{
// when invalid, don't let the dialog close:
this.DialogResult = DialogResult.None;
return;
}
// Validate
children
ValidateAllChildren( c );
}
}
Which collection is best?, well it depends. Different collections have different performance characteristics and are optimized for different actions. The .NET Framework supports many familiar collections: lists, arrays, queue, stack, and others. C# supports multidimensional arrays, which have performance characteristics that differ from either single-dimensional arrays or jagged arrays. The framework also includes many specialized collections; look through those before you build your own.
To pick the right collection for your proposed use, you need to
consider the actions you'll most often perform on that collection. To produce a
resilient program, you'll rely in the interfaces that are implemented by the
collection classes so that you can substitute a different collection when you
find that your assumptions about the usage of the collection were incorrect (see
Item 19).
The .NET Framework has three different kinds of collections: arrays, array-like
collections, and hash-based containers. Arrays are the simplest and generally
the fastest, so let's start there. This is the collection type you'll use most
often.
class Item40
{
/// <summary>
/// Using a 1-D array
/// </summary>
public void Test1DArray()
{
// Create and initialize an array using object
int[] aInts = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// You can iterate an array using foreach
foreach (int n in aInts)
Trace.WriteLine(n.ToString());
// Or you can iterate an array using an enumerator
IEnumerator it = aInts.GetEnumerator();
while (it.MoveNext())
Trace.WriteLine( Convert.ToString(it.Current));
}
/// <summary>
/// With jagged arrays, each element is an array with a size that is typically
/// different than other elements. Use jagged arrays when you need to create
/// differently sized arrays of arrays. Initializing the jagged array requires
/// multiple initialization statements. In the example below, you need three
/// statements. Larger arrays or arrays with more dimensions require more extensive
/// initialization code. You must write code by hand.
/// </summary>
public void TestJaggedArrays()
{
// Create a jagged array. Arrays of value types contain 0 at each valid
// index. Arrays of reference types contain null at each valid index
int[][] aJagged = new int[3][]; // Examine in Watch window
// Initialize a jagged array. Note that each element of a Jagged
// is an array of different length
aJagged[0] = new int [] {1,2,3};
aJagged[1] = new int[] {4,5,7,8};
aJagged[2] = new int[] {9, 10, 11, 12, 13};
// Traversing a jagged array require a foreach statement for each dimension
// a Jagged is a 2D jagged array
foreach (int[] aN in aJagged)
foreach ( int n in aN)
Trace.WriteLine(n.ToString());
}
/// <summary>
/// The length of each dimension in a multidimensional array is always constant.
/// The compiler utilizes this property to generate more efficient initialization
/// code than that for jagged arrays
/// </summary>
public void TestMultiDimensionalArrays()
{
// Create a 2x4 2D array (2 rows and 4 columns). Arrays of value types contain
// 0 at each valid index. Arrays of reference types contain null at each valid
// index
int[,] aMulti = new int[2, 4] { {1,2,3,4}, {5,6,7,8}}; // Examine in Watch window
// Traverse a multi-dimensional array. Requires a single foreach statement
// unlike jagged array. Note that it traverses all columns in first row,
// then all columns in second row
foreach (int n in aMulti)
Trace.WriteLine(n.ToString());
}
}
Suppose you need to build a mechanism to dynamically add menu items and command handlers to a running software system. The requirements are for these dynamic menus simple: Drop an assembly into a directory, and the program will find out about it and add new menu items for the new command. This is one of those jobs that is best handled with reflection as existing assemblies needs to interact with assemblies that have not yet been written.
When you build systems that rely on reflection, you should
define custom attributes for the types, methods, and properties you intend to
use to make them easier to access. The custom attributes indicate how
you intended the method to be used at runtime. Attributes can test some of the
properties of the target. Testing these properties minimizes the likelihood of
mistyping that can happen with reflection.
Let's begin with the code you need to create the dynamic menus framework:
You need to load an assembly using the Assembly.LoadFrom() function.
You need to find the types that might provide menu handlers.
You need to create an object of the proper type. Type.GetConstructor() and ConstructorInfo.Invoke() are the tools for that.
You need to find a method that matches the menu command event handler signature.
Finally, you need to figure out where on the menu to add the new text, and what the text should be.
Attributes make many of these tasks easier. By tagging different
classes and event handlers with custom attributes, you greatly simplify your
task of finding and installing those potential command handlers. In our
dynamic menus framework, an attribute class marks the types that have
command handlers:
// Define the Command Handler Custom
Attribute. Always mark an attribute class with
// the [AttributeUsage] attribute; it tells other programmers and the compiler
where
// your attribute can be use
[AttributeUsage( AttributeTargets.Class )]
public class CommandHandlerAttribute : Attribute
{
public CommandHandlerAttribute(
)
{
}
}
You call GetCustomAttributes to determine whether a type has the
[CommandHandler] attribute. Only those types are candidates for add-ins:
// Get all the assemblies from some designated add-in
directory
string[] assemblies = GetDyanmicMenuAssemblies();
foreach ( string
assemblyFile in assemblies )
{
Assembly asm = Assembly.LoadFrom( assemblyFile );
// Find and install command handlers from the assembly.
foreach( System.Type t
in asm.GetExportedTypes( ))
{
if (t.GetCustomAttributes(typeof(
CommandHandlerAttribute ), false ).Length > 0 )
{
// Found the
command handler attribute on this type. This type implements a command handler.
// configure
and add it.
}
//
Else, not a command handler. Skip it.
}
}
Now we define a new attribute that add-in authors will attach to
each command handler. To tag a command handler, you define an attribute that
marks a property as a command handler and declares the text for the menu item
and the text for the parent menu item. The [DynamicCommand]
attribute shown below is constructed with two parameters: the command text and
the text of the parent menu. This attribute class is tagged so that it can be
applied only to properties. The command handler must be exposed as a property in
the class that provides access to the command handler.
[AttributeUsage( AttributeTargets.Property ) ]
public class DynamicMenuAttribute :
System.Attribute
{
private string MenuText {get;
set; }
private string ParentText {get;
set; }
public DynamicMenuAttribute(
string CommandText, string
ParentText )
{
MenuText = CommandText;
ParentText = ParentText;
}
}
Now you'll build a sample command handler. First, you tag the type with the
[CommandHandler] attribute. Inside the
CmdHandler class, you add a property to retrieve the
command handler. That property should be tagged with the [DynamicMenu]
attribute:
[ CommandHandler ]
public class CmdHandler
{
[DynamicMenu( "Test Command", "Parent Menu" )]
public EventHandler CmdFunc
{
get
{
if ( theCmdHandler == null
)
theCmdHandler = new System.EventHandler(this.DynamicCommandHandler);
return theCmdHandler;
}
}
private void
DynamicCommandHandler( object sender, EventArgs
args )
{
...
}
}
That's it. This example shows you how you can utilize attributes to simplify
programming idioms that use reflection. You tagged each type that provided a
dynamic command handler with an attribute. That made it easier to find the
command handlers when you dynamically loaded the assembly. By applying
AttributeTargets (another attribute), you limit
where the dynamic command attribute can be applied. This simplifies the
difficult task of finding the sought types in a dynamically loaded assembly: You
greatly decrease the chance of using the wrong types. It's still not simple
code, but it is a little more palatable than without attributes.
When you use reflection, you circumvent C#'s type safety. Instead, the Invoke members use parameters and return values typed as System.Object. You must make sure the proper types are used at runtime. In short, using reflection makes it much easier to build dynamic programs, but it is also much easier to build broken programs. Often, with a little thought, you can minimize or remove the need for reflection by creating a set of interface definitions that express your assumptions about a type. There is nothing magical about reflection. It is a means of dynamically interacting with other binary components. In most cases, you don't need the flexibility of reflection because other alternatives are more maintainable.
For example, consider the case of creating instances of a given
type. You can often accomplish the same result using a class factory. Consider
this code fragment, which creates an instance of MyType
by calling the default constructor using reflection:
// Usage:Create a new object using
reflection:
Type t = typeof( MyType );
MyType obj = NewInstance( t ) as MyType;
// Example factory function, based on Reflection:
object NewInstance( Type t )
{
// Find the default constructor:
ConstructorInfo ci = t.GetConstructor(
new Type[ 0 ] );
if
( ci != null )
// Invoke
default constructor, and return the new object.
return
ci.Invoke( null );
// If it failed, return null.
return null;
}
The code examines the type using reflection and invokes the default constructor
to create the object. If you need to create a type at runtime without any
previous knowledge of the type, this is the only option. This is brittle code
that relies on the presence of a default constructor. It still compiles if you
remove the default constructor from MyType. You must
perform runtime testing to catch any problems that arise.
Reflection is a powerful late-binding mechanism. The .NET Framework uses it to implement data binding for both Windows- and web-based controls. However, in many less general uses, creating code using class factories, delegates, and interfaces will produce more maintainable systems.
You need to be very thoughtful about when you create your own
specific exception classes in your C# applications. When developers using your
libraries write catch clauses, they differentiate
actions based on the specific runtime type of the exception. Each different
exception class can have a different set of actions taken:
try
{
Foo( );
Bar( );
}
catch( MyFirstApplicationException e1 )
{
FixProblem( e1 );
}
catch( AnotherApplicationException e2 )
{
ReportErrorAndContinue( e2 );
}
catch( YetAnotherApplicationException e3 )
{
ReportErrorAndShutdown( e3 );
}
catch( Exception e )
{
ReportGenericError( e );
}
catch
{
CleanupResources( );
}
Different catch clauses can exist for different runtime types of exceptions.
You, as a library author, must create or use different exception classes when
catch clauses might take different actions. If you don't, your users are left
with only unappealing options, such as the following:
try
{
Foo( );
Bar( );
}
catch( Exception e )
{
switch( e.TargetSite.Name )
{
case "Foo":
FixProblem( e
);
break;
case
"Bar":
ReportErrorAndContinue( e );
break;
default:
ReportErrorAndShutdown( e );
break;
}
}
finally
{
CleanupResources( );
}
That's far less appealing than using multiple catch clauses. It's very brittle
code: If you change the name of a routine, it's broken
The reason for different exception classes is to make it
easier to take different actions when your users write catch handlers.
Look for those error conditions that might be candidates for some kind of
recovery action, and create specific exception classes to handle those actions
You do have very specific responsibilities when you create a new exception
class. You should always derive your exception classes from the
System.ApplicationException class, not the
System.Exception class. When you create a new
exception class, create all constructors defined in
System.ApplicationException class. Different situations call for the
different methods of constructing exceptions. You delegate the work to the base
class implementation:
public class
MyAssemblyException : ApplicationException
{
public MyAssemblyException( ) :
base( )
{
}
public MyAssemblyException(
string s ) : base( s )
{}
public MyAssemblyException(
string s, Exception e) : base( s, e )
{
}
protected MyAssemblyException(
SerializationInfo info, StreamingContext cxt ) : base(
info, cxt )
{
}
...
}
You should provide your own library's information when you generate the
exception. Throw your own specific exception, and include the original exception
as its InnerException property. You can provide as
much extra information as you can generate:
public double DoSomeWork( )
{
try
{
// This might throw an exception
defined in the third party library:
return
ThirdPartyLibrary.ImportantRoutine( );
}
catch( Exception e )
{
string
msg = string.Format("Problem with {0} using library", this.ToString( ));
throw new
DoingSomeWorkException( msg, e );
}
}
This new version creates more information at the point where the problem is
generated. As long as you have created a proper ToString()
method (see Item 5 Always
Override
ToString()), you've created an exception
that describes the complete state of the object that generated the problem. More
than that, the inner exception shows the root cause of the problem: something in
the third-party library you used.
Your application will throw exceptions—hopefully not often, but it will happen.
If you don't do anything specific, your application will generate the default
.NET Framework exceptions whenever something goes wrong in the methods you call
on the core framework. Providing more detailed information will go a long way to
enabling you and your users to diagnose and possibly correct errors in the
field. You create different exception classes when different corrective actions
are possible and only when different actions are possible. You create
full-featured exception classes by providing all the constructors that the base
exception class supports. You use the InnerException
property to carry along all the error information generated by lower-level error
conditions.