CHAPTER 7

image

Other Class Details

This chapter discusses some of the miscellaneous issues of classes, including constructors, nesting, and overloading rules.

Nested Classes

Sometimes, it is convenient to nest classes within other classes, such as when a helper class is used by only one other class. The accessibility of the nested class follows similar rules to the ones outlined for the interaction of class and member modifiers. As with members, the accessibility modifier on a nested class defines what accessibility the nested class has outside of the nested class. Just as a private field is visible only within a class, a private nested class is visible only from within the class that contains it.

In the following example, the Parser class has a Token class that it uses internally. Without using a nested class, it might be written as follows:

public class Parser
{
    Token[] tokens;
}
public class Token
{
    string name;
}

In this example, both the Parser and Token classes are publicly accessible, which isn’t optimal. Not only is the Token class one more class taking up space in the designers that list classes, but it isn’t designed to be generally useful. It’s therefore helpful to make Token a nested class, which will allow it to be declared with private accessibility, hiding it from all classes except Parser.

Here’s the revised code:

public class Parser
{
    Token[] tokens;
    private class Token
    {
       string name;
    }
}

Now, nobody else can see Token. Another option would be to make Token an Internal class so that it wouldn’t be visible outside the assembly, but with that solution, it would still be visible inside the assembly.

Making Token an internal class also misses out on an important benefit of using a nested class. A nested class makes it very clear to those reading the source code that the Token class can safely be ignored unless the internals for Parser are important. If this organization is applied across an entire assembly, it can help simplify the code considerably.

Nesting can also be used as an organizational feature. If the Parser class were within a namespace named Language, you might require a separate namespace named Parser to nicely organize the classes for Parser The Parser namespace would contain the Token class and a renamed Parser class. By using nested classes, the Parser class could be left in the Language namespace and contain the Token class.

Other Nesting

Classes aren’t the only types that can be nested; interfaces, structs, delegates, and enums can also be nested within a class.

Anonymous Types

An anonymous type is a class that does not have a user-visible name. Here’s an example:

var temporary = new { Name = "George", Charactistic = "Curious" };

Such a type can be used to hold temporary results within the scope of a single method. Because the type does not have a name, it cannot be used as a parameter type on a method or as a return value.1

Anonymous types are rarely used directly but are the result of the Select() Linq method. See Chapter 28 for more information.

Creation, Initialization, Destruction

In any object-oriented system, dealing with the creation, initialization, and destruction of objects is very important. In the .NET Runtime, the programmer can’t control the destruction of objects, but it’s helpful to know the other areas that can be controlled.

Constructors

If there are no constructors, the C# compiler will create a public parameter-less constructor.

A constructor can invoke a constructor of the base type by using the base syntax, like this:

using System;
public class BaseClass
{
    public BaseClass(int x)
    {
       this.x = x;
    }
    public int X
    {
       get
       {
            return(x);
       }
    }
    int x;
}
public class Derived: BaseClass
{
    public Derived(int x): base(x)
    {
    }
}
class Test
{
    public static void Main()
    {
       Derived d = new Derived(15);
       Console.WriteLine("X = {0}", d.X);
    }
}

In this example, the constructor for the Derived class merely forwards the construction of the object to the BaseClass constructor.

Sometimes it’s useful for a constructor to forward to another constructor in the same object, as in the following example:

using System;
class MyObject
{
    public MyObject(int x)
    {
       this.x = x;
    }
    public MyObject(int x, int y): this(x)
    {
       this.y = y;
    }
    public int X
    {
       get
       {
            return(x);
       }
    }
    public int Y
    {
       get
       {
            return(y);
       }
    }
    int x;
    int y;
}
class Test
{
    public static void Main()
    {
       MyObject my = new MyObject(10, 20);
       Console.WriteLine("x = {0}, y = {1}", my.X, my.Y);
    }
}

Private Constructors

Private constructors are—not surprisingly—usable only from within the class on which they’re declared. If the only constructor on the class is private, this prevents any user from instantiating an instance of the class, which is useful for classes that are merely containers of static functions (such as System.Math, for example).

Private constructors are also used to implement the singleton pattern, when there should be only a single instance of a class within a program. This is usually done as follows:

public class SystemInfo
{
    static SystemInfo cache = null;
    static object cacheLock = new object();
    private SystemInfo()
    {
       // useful stuff here...
    }
    public static SystemInfo GetSystemInfo()
    {
       lock(cacheLock)
       {
            if (cache == null)
            {
                cache = new SystemInfo();
            }
            return(cache);
       }
    }
}

This example uses locking to make sure the code works correctly in a multithreaded environment. For more information on locking, see Chapter 31.

Initialization

If the default value of the field isn’t what is desired, it can be set in the constructor. If there are multiple constructors for the object, it may be more convenient—and less error-prone—to set the value through an initializer rather than setting it in every constructor.

Here’s an example of how initialization works:

public class Parser       // Support class
{
    public Parser(int number)
    {
       this.number = number;
    }
    int number;
}
class MyClass
{
    public int counter = 100;
    public string heading = "Top";
    private Parser parser = new Parser(100);
}

This is pretty convenient; the initial values can be set when a member is declared. It also makes class maintenance easier since it’s clearer what the initial value of a member is.

To implement this, the compiler adds code to initialize these functions to the beginning of every constructor.

image Tip  As a general rule, if a member has differing values depending on the constructor used, the field value should be set in the constructor. If the value is set in the initializer, it may not be clear that the member may have a different value after a constructor call.

Destructors

Strictly speaking, C# doesn’t have destructors, at least not in the way that most developers think of destructors, where the destructor is called when the object is deleted.

What is known as a destructor in C# is known as a finalizer in some other languages and is called by the garbage collector when an object is collected. The programmer doesn’t have direct control over when the destructor is called, and it is therefore less useful than in languages such as C++. If cleanup is done in a destructor, there should also be another method that performs the same operation so that the user can control the process directly.

When a destructor is written in C#, the compiler will automatically add a call to the base class’s finalizer (if present).

For more information on this, see the section on garbage collection in Chapter 38. If garbage collection is new to you, you’ll probably want to read that chapter before delving into the following section.

Managing Nonmemory Resources

The garbage collector does a good job of managing memory resources, but it doesn’t know anything about other resources, such as database handles, graphics handles, and so on. Because of this, classes that hold such resources will have to do the management themselves.

In many cases, this isn’t a real problem; all that it takes is writing a destructor for the class that cleans up the resource.

using System;
using System.Runtime.InteropServices;
class ResourceWrapper
{
    int handle = 0;
    public ResourceWrapper()
    {
       handle = GetWindowsResource();
    }
    ∼ResourceWrapper()
    {
       FreeWindowsResource(handle);
       handle = 0;
    }
    [DllImport("dll.dll")]
    static extern int GetWindowsResource();

    [DllImport("dll.dll")]
    static extern void FreeWindowsResource(int handle);
}

Some resources, however, are scarce and need to be cleaned up in a more timely manner than the next time a garbage collection occurs. Since there’s no way to call finalizers automatically when an object is no longer needed,2 it needs to be done manually.

In the .NET Framework, objects can indicate that they hold on to such resources by implementing the IDisposable interface, which has a single member named Dispose(). This member does the same cleanup as the finalizer, but it also needs to do some additional work. If either its base class or any of the other resources it holds implement IDisposable, it needs to call Dispose() on them so that they also get cleaned up at this time.3 After it does this, it calls GC.SuppressFinalize() so that the garbage collector won’t bother to finalize this object. Here’s the modified code:

using System;
using System.Runtime.InteropServices;
class ResourceWrapper: IDisposable
{
    int handle = 0;
    bool disposed;
    public ResourceWrapper()
    {
       handle = GetWindowsResource();
    }
    // does cleanup for this object only
    protected virtual void Dispose(bool disposing)
    {
       if (!disposed)
       {
            if (disposing)
            {
                // call Dispose() for any managed resources
            }
            //dispose unmanaged resources
            FreeWindowsResource(handle);
            handle = 0;
            disposed = true;
       }
       //if there was a base class you would use the following line
       //base.Dispose(disposing);
    }
    ∼ResourceWrapper()
    {
       Dispose(false);
    }
       // dispose cleans up its object, and any objects it holds
       // that also implement IDisposable.
    public void Dispose()
    {
       Dispose(true);
       GC.SuppressFinalize(this);
    }
    [DllImport("dll.dll")]
    static extern int GetWindowsResource();

    [DllImport("dll.dll")]
    static extern void FreeWindowsResource(int handle);
}

If your object has semantics where another name is more appropriate than Dispose() (a file would have Close(), for example), then you should implement IDisposable using explicit interface implementation. You would then have the better-named function forward to Dispose().

This pattern is complex and easy to get wrong. If you are dealing with handle classes, you should instead use one of the handle classes defined in the Microsoft.Win32.SafeHandles namespace or one of the types derived from System.Runtime.InteropServices.SafeHandle.

IDisposable and the Using Statement

When using classes that implement IDisposable, it’s important to make sure Dispose() gets called at the appropriate time. When a class is used locally, this is easily done by wrapping the usage in try-finally, such as in this example:

ResourceWrapper rw = new ResourceWrapper();
try
{
    // use rw here
}
finally
{
    if (rw != null)
    {
       ((IDisposable) rw).Dispose();
    }
}

The cast of the rw to IDisposable is required because ResourceWrapper could have implemented Dispose() with explicit interface implementation.4 The try-finally is a bit ugly to write and remember, so C# provides the using statement to simplify the code, like this:

using (ResourceWrapper rw = new ResourceWrapper())
{
    // use rw here
}

The using variant is equivalent to the earlier example using try-finally. If two or more instances of a single class are used, the using statement can be written as follows:

using (ResourceWrapper rw = new ResourceWrapper(), rw2 = new ResourceWrapper())

For different classes, two using statements can be placed next to each other.

using (ResourceWrapper rw = new ResourceWrapper())
using (FileWrapper fw = new FileWrapper())

In either case, the compiler will generate the appropriate nested try-finally blocks.

IDisposable and Longer-Lived Objects

The using statement provides a nice way to deal with objects that are around for only a single function. For longer-lived objects, however, there’s no automatic way to make sure Dispose() is called.

It’s fairly easy to track this through the finalizer, however. If it’s important that Dispose() is always called, it’s possible to add some error checking to the finalizer to track any such cases. This could be done with a few changes to the ResourceWrapper class.

    static int finalizeCount = 0;
    ∼ResourceWrapper()
    {
       finalizeCount++;
       Dispose(false);
    }
    [Conditional("DEBUG")]
    static void CheckDisposeUsage(string location)
    {
       GC.Collect();
       GC.WaitForPendingFinalizers();
       if (finalizeCount != 0)
       {
            finalizeCount = 0;
            throw new Exception("ResourceWrapper(" + location +
            ": Dispose() = " + finalizeCount);
       }
    }

The finalizer increments a counter whenever it is called, and the CheckDisposeUsage() routine first makes sure that all objects are finalized and then checks to see whether there were any finalizations since the last check. If so, it throws an exception.5

Static Fields

It is sometimes useful to define members of an object that aren’t associated with a specific instance of the class but rather with the class as a whole. Such members are known as static members.

A static field is the simplest type of static member; to declare a static field, simply place the Static modifier in front of the variable declaration. For example, the following could be used to track the number of instances of a class that were created:

using System;
class MyClass
{
    public MyClass()
    {
       instanceCount++;
    }
    public static int instanceCount = 0;
}
class Test
{
    public static void Main()
    {
       MyClass my = new MyClass();
       Console.WriteLine(MyClass.instanceCount);
       MyClass my2 = new MyClass();
       Console.WriteLine(MyClass.instanceCount);
    }
}

The constructor for the object increments the instance count, and the instance count can be referenced to determine how many instances of the object have been created. A static field is accessed through the name of the class rather than through the instance of the class; this is true for all static members.

image Note  This is unlike the VB/C++ behavior where a static member can be accessed through either the class name or the instance name. In VB and C++, this leads to some readability problems, because it’s sometimes not clear from the code whether an access is static or through an instance.

Static Member Functions

The previous example exposes an internal field, which is usually something to be avoided. It can be restructured to use a static member function instead of a static field, like in the following example:

using System;
class MyClass
{
    public MyClass()
    {
       instanceCount++;
    }
    public static int GetInstanceCount()
    {
       return instanceCount;
    }
    static int instanceCount = 0;
}
class Test
{
    public static void Main()
    {
       MyClass my = new MyClass();
       Console.WriteLine(MyClass.GetInstanceCount());
    }
}

This now uses a static member function and no longer exposes the field to users of the class, which increases future flexibility. Because it is a static member function, it is called using the name of the class rather than the name of an instance of the class.

In the real world, this example would probably be better written using a static property, which is discussed Chapter 19.

Static Constructors

Just as there can be other static members, there can also be static constructors. A static constructor will be called before the first instance of an object is created. It is useful to do setup work that needs to be done only once.

image Note  Like many other things in the .NET Runtime world, the user has no control over when the static ­constructor is called; the runtime guarantees only that it is called sometime after the start of the program and before the first instance of an object is created. Therefore, it can’t be determined in the static constructor that an instance is about to be created.

A static constructor is declared simply by adding the static modifier in front of the constructor definition. A static constructor cannot have any parameters.

using System;
class MyClass
{
    static MyClass()
    {
       Console.WriteLine("MyClass is initializing");
    }
}

There is no static analog of a destructor.

Constants

C# allows values to be defined as constants. For a value to be a constant, its value must be something that can be written as a constant. This limits the types of constants to the built-in types that can be written as literal values.

Not surprisingly, putting const in front of a variable means that its value cannot be changed. Here’s an example of some constants:

using System;
enum MyEnum
{
    Jet
}
class LotsOLiterals
{
       // const items can't be changed.
       // const implies static.
    public const int value1 = 33;
    public const string value2 = "Hello";
    public const MyEnum value3 = MyEnum.Jet;
}
class Test
{
    public static void Main()
    {
       Console.WriteLine("{0} {1} {2}",
                LotsOLiterals.value1,
                LotsOLiterals.value2,
                LotsOLiterals.value3);
    }
}

Read-Only Fields

Because of the restriction on constant types being knowable at compile time, const cannot be used in many situations.

In a Color class, it can be useful to have constants as part of the class for the common colors. If there were no restrictions on const, the following would work:

// error
class Color
{
    public Color(int red, int green, int blue)
    {
       m_red = red;
       m_green = green;
       m_blue = blue;
    }
    int m_red;
    int m_green;
    int m_blue;
       // call to new can't be used with static
    public const Color Red = new Color(255, 0, 0);
    public const Color Green = new Color(0, 255, 0);
    public const Color Blue = new Color(0, 0, 255);
}
class Test
{
    static void Main()
    {
       Color background = Color.Red;
    }
}

This clearly doesn’t work because the static members Red, Green, and Blue can’t be calculated at compile time. But making them normal public members doesn’t work either; anybody could change the red value to olive drab or puce.

The readonly modifier is designed for exactly that situation. By applying readonly, the value can be set in the constructor or in an initializer, but it can’t be modified later.

Because the color values belong to the class and not a specific instance of the class, they’ll be initialized in the static constructor, like so:

class Color
{
    public Color(int red, int green, int blue)
    {
       m_red = red;
       m_green = green;
       m_blue = blue;
    }
    int m_red;
    int m_green;
    int m_blue;

    public static readonly Color Red;
    public static readonly Color Green;
    public static readonly Color Blue;

       // static constructor
    static Color()
    {
       Red = new Color(255, 0, 0);
       Green = new Color(0, 255, 0);
       Blue = new Color(0, 0, 255);
    }
}
class Test
{
    static void Main()
    {
       Color background = Color.Red;
    }
}

This provides the correct behavior.

If the number of static members were high or creating the members was was expensive (in either time or memory), it might make more sense to declare them as readonly properties so that members could be constructed on the fly as needed.

On the other hand, it might be easier to define an enumeration with the different color names and return instances of the values as needed.

class Color
{
    public Color(int red, int green, int blue)
    {
       m_red = red;
       m_green = green;
       m_blue = blue;
    }
    public enum PredefinedEnum
    {
       Red,
       Blue,
       Green
    }
    public static Color GetPredefinedColor(
    PredefinedEnum pre)
    {
       switch (pre)
       {
            case PredefinedEnum.Red:
                return new Color(255, 0, 0);
            case PredefinedEnum.Green:
                return new Color(0, 255, 0);
            case PredefinedEnum.Blue:
                return new Color(0, 0, 255);
            default:
                return new Color(0, 0, 0);
       }
    }
    int m_red;
    int m_blue;
    int m_green;
}
class Test
{
    static void Main()
    {
       Color background =
            Color.GetPredefinedColor(Color.PredefinedEnum.Blue);
    }
}

This requires a little more typing, but there isn’t a start-up penalty or lots of objects taking up space. It also keeps the class interface simple; if there were 30 members for predefined colors, the class would be much harder to understand.6

image Note  Experienced C++ programmers are probably cringing at the previous code example. It embodies one of the classic problems with the way C++ deals with memory management. Passing back an allocated object means the caller has to free it. It’s pretty easy for the user of the class to either forget to free the object or lose the pointer to the object, which leads to a memory leak. In C#, however, this isn’t an issue, because the runtime handles memory allocation. In the preceding example, the object created in the Color.GetPredefinedColor() function gets copied immediately to the background variable and then is available for collection when background goes out of scope.

Extension Methods

Consider the following scenario. Your company has some files to process and in them are some strangely formatted headers.

#Name#,#Date#,#Age#,#Salary#

You need to extract the list of headers and therefore add a method to the class.

static List < string > ExtractFields(string fieldString)
{
    string[] fieldArray = fieldString.Split(','),
    List < string > fields = new List < string > ();
    foreach (string field in fieldArray)
    {
       fields.Add(field.Replace("#", ""));
    }
    return fields;
}

This then allows you to write the following code:

string test = "#Name#,#Date#,#Age#,#Salary#";
List < string > fields = ExtractFields(test);
foreach (string field in fields)
{
    Console.WriteLine(field);
}

It turns out that you need to perform this operation in other places in your code, and you therefore move it to a utility class and write the following code to use it:

List < string > fields = StringHelper.ExtractFields(test);

This works but is more than a bit clunky. What you want is a way to make ExtractFields() look like it is defined on the String class, which is exactly what extension methods allow you to do.

public static List < string > ExtractFields(this string fieldString) {}

Putting this in front of the first parameter of a static method in a static class converts that method into an extension method, allowing the methods to be called using the same syntax as the methods defined on the class.

List < string > fields = test.ExtractFields();

This gives a nice simplification.

Usage Guidelines

Extension methods are a very powerful feature and are a requirement for advanced features such as Linq,7 but it can also make code less clear. Before using an extension method, the following questions should be asked:

  • Is this method a general-purpose operation on the class?
  • Will it be used often enough for developers to remember that it is there?
  • Can it be named in a way that makes its function clear, and does that name fit well with the existing methods on the class?

The answers depend on context; a method that is general-purpose in one scenario may not be general-purpose in others.

My advice is not to implement extension methods right away; write them as helpers, and after you have used them for a while, it will be obvious whether they make sense as extension methods.

Object Initializers

Object initializers can be used in place of constructor parameters. Consider the following class:

public class Employee
{
    public string Name;
    public int Age;
    public decimal Salary
}

Using the class is quite simple.

Employee employee = new Employee();
employee.Name = "Fred";
employee.Age = 35;
employee.Salary = 13233m;

But it does take a lot of code to set the items. The traditional way of dealing with this is to write a constructor.

public Employee(string name, int age, decimal salary)
{
    Name = name;
    Age = age;
    Salary = salary;
}

That changes the creation to the following:

Employee emp = new Employee("Fred", 35, 13233m);

C# supports an alternate syntax that removes the constructor and allows the properties to be mentioned by name.

Employee employee = new Employee() { Name = "Fred", Age = 35, Salary = 13233m };

This appears to be a nice shortcut; instead of having to create a constructor, you just allow the user to set the properties they want to set, and everybody is happy. Unfortunately, this construct also allows code such as this:

Employee employee = new Employee() { Age = 35};

which sets the Age while leaving the Name and Salary with their default values, which is clearly a nonsensical state for the object.

Basically, you have lost control about the possible states of the Employee object; instead of one state where the name, age, and salary are all set, you now have eight separate states, defined by all the possible combinations of each property being set or not.8

That puts you out of the realm of good object-oriented design.9 It would be much better to have constructors enforce a specific contract around the creation of the object, change the properties to be read-only, and end up with an immutable object that is much easier to understand.

WHY DOES C# ALLOW OBJECT INITIALIZERS?

If object initializers allow developers to write code that is less good than the constructor alternative, why are they allowed in the language?

One of the big features in C# is the Linq syntax (see Chapter 29), and the Linq syntax requires a way of automatically creating a temporary type known as an anonymous type. The creation of an instance of an anonymous type requires a way of defining both the fields of the anonymous types and setting the values of each field, and that is where the object initializer syntax came from.

At that point, there was a choice. C# either could allow object initializers to be used for other classes or could impose an arbitrary restriction10 where object initializers were allowed for anonymous classes but not for other classes, which would make the language a little more complicated.

Static Classes

Some classes—System.Environment is a good example—are just containers for a bunch of static methods and properties. It would be of no use to ever create an instance of such a class, and this can easily be accomplished by making the constructor of the class private.

This does not, however, make it easy to know the reason that there are no visible constructors; other classes have instances that can be created only through factory methods. Users may try to look for such factory methods, and maintainers of such a class may not realize they cannot be instantiated and accidentally add instance methods to the class, or they may intend to make a class static and forget to do so.

Static classes prevent this from happening. When the static keyword is added to a class:

static class Utilities
{
    static void LogMessage(string message) {}
}

it is easy for the user of the class to see that it is static, and the compiler will generate an error if instance methods are added.

Partial Classes and Methods

Code generators (which are programs that generate code, often from a UI design program) have a problem.

They need a place for the user to write the code that will extend the generated code to do something useful. There are two way this has traditionally been done. The first is to structure the generated code into sections; one section says “put your code here,” and the other section says “generated code; do not touch.”11 This solution is unsatisfying; what should be implementation details are in the user’s face, and the files are much bigger than they need to be, not to mention that the user can accidentally modify that code.

The second solution is based on inheritance; the user class is either the base class or the derived class of the generated class. This solves the “ugly code in my face” issue, at the cost of added complexity of base methods, derived methods, virtual methods that are virtual only because of this schema, and how everything fits together.

C# provides a third solution: partial classes and partial methods. A partial class is simply a class that is written in two (or more) separate parts. Consider the following:

partial class Saluter
{
    int m_saluteCount;
    public Saluter(int saluteCount)
    {
       m_saluteCount = saluteCount;
    }
    public void Ready()
    {
       Console.WriteLine("Ready");
    }
}
partial class Saluter
{
    public void Aim()
    {
       Console.WriteLine("Aim");
    }
}
partial class Saluter
{
    public void Fire(int count)
    {
       for (int i = 0; i < m_saluteCount; i++)
       {
            Console.WriteLine("Fire");
       }
    }
}

Here are three different partial classes of the Saluter class (in real partial class scenarios, they would be in three separate files). At compilation time, the compiler will glue all of the partial classes together and generate a single Saluter class, which can then be used as expected.

Saluter saluter = new Saluter(21);
saluter.Ready();
saluter.Aim();
saluter.Fire();

image Note  Since partial classes are a compiler feature, all partial parts of a class must be compiled at the same time.

Partial classes solve most of the issues with code generation; you can sequester the generated code into a separate file12 so it’s not annoying while still keeping the approach clean and simple. There is, however, one more issue. Consider the following:

// EmployeeForm.Designer.cs
partial class EmployeeForm
{
    public void Initialize()
    {
       StartInitialization();
       FormSpecificInitialization();
       FinishInitialization();
    }
    void StartInitialization() { }
    void FinishInitialization() { }
}
// EmployeeForm.cs
partial class EmployeeForm
{
    void FormSpecificInitialization()
    {
       // add form-specific initialization code here.
    }
}

In this situation, the form needs to give the user the opportunity to perform operations before the form is fully initialized, so it calls FormSpecificInitialization(). Unfortunately, if the user doesn’t need to do anything, the empty method is still present in the user’s code. What is needed is a way to make this call only if the user wants. That way is through partial methods, by adding a partial method to the generated class.

partial void FormSpecificInitialization();

The compiler now knows that if there is no implementation at compilation time, the method should be removed from the source.13 There are a few restrictions; because the method might not be there, it can’t communicate anything back to the caller, which means no return values (it must be a void method) and no out parameters.

PARTIAL CLASSES AND BIG FILES

It has been suggested that partial classes are useful to break big classes down into smaller, more manageable chunks. While this is a possible use of partial classes, it is trading one kind of complexity (too many lines in the file) for a different kind of complexity (a class implementation spread across multiple files). In the vast majority of cases, it’s far better to refactor the class that is too big into several smaller classes that do less.14

1 You can pass it to a method that takes the type object, though at that point there isn’t a way to access the values directly without using reflection.

2 The discussion why this isn’t possible is long and involved. In summary, lots of really smart people tried to make it work and couldn’t.

3 This is different from the finalizer. Finalizers are responsible only for their own resources, while Dispose() also deals with referenced resources.

4 See Chapter 10.

5 It might make more sense to log this to a file, depending on the application.

6 For an explanation on why a default case is important, see Chapter 20.

7 Take a Linq expression with three clauses, and try writing it without using extension methods, and you’ll see what I mean. The syntax is much more confusing, and the operations have to be written in reverse order.

8 Each property can be set or unset, and 2 cubed is equal to 8.

9 I was tempted to use the word travesty in reference to this approach.

10 Which would have to be designed, coded, tested, documented, and so on.

11 Or the somewhat less descriptive but ever-so-enjoyable “Here be dragons!”

12 These are typically named something like file.designer.cs.

13 Partial methods use the same infrastructure as conditional methods, which will be discussed in Chapter 41.

14 If you are building a library where you’ve measured the performance cost of multiple classes and can’t afford it, then you have my blessing to use partial classes to make your life a bit easier.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.39.190