This chapter contains essential, general C# knowledge that doesn’t fit easily into other chapters: from the multitude of ways to declare variables to the different types of multidimensional arrays and some of the “unusual” operators in C#.
Solution: C# provides many ways to declare and define variables. We’ll start with the simplest:
Solution: Type inference allows you to let the compiler decide what type a local variable is (it cannot be used for class fields). It is important to realize that variables are still strongly typed—var
is not equivalent to Object
. You are merely giving the job of figuring out the type to the compiler. Once that’s done, the type can’t change during runtime.
This program produces the following output:
Apart from the benefit of convenience when typing (by omitting repeating type names), implicit typing is highly useful when using LINQ (see Chapter 21, “LINQ”).
There is some amount of controversy about the usage of var
. On one hand, it is mighty convenient when you don’t want to figure out and write out the type of an object you won’t be using outside of a narrow scope (especially when it’s long or has type parameters). On the other hand, it can definitely be overused.
My favorite place to use var
is in foreach
, so I don’t have to figure out what the precise type in the collection is, as in the following example:
Solution: Use the dynamic
keyword for your variables and method parameters.
This functionality is most useful in dynamic languages that require runtime binding, but it can be useful in other occasions. Here’s a simple demo:
For other examples, see the next section and in Chapter 24, “Reflection and Creating Plugins,” to see how dynamic typing can make method execution on reflected types easier.
While the dynamic keyword gives us dynamic behavior, behind the scenes, the Dynamic Language Runtime (DLR) uses compiler tricks and reflection to bind the calls involving dynamic types to standard static types.
Solution: Use dynamic typing to have the runtime do the checking and binding for you, as in the following example, which uses the Excel 2007 COM component.
Solution: There are many variations of array declaration and definition syntax.
These additional ways of initializing an array actually apply to anything that implements IEnumerable (See Chapter 10, “Collections”, for examples of this). This is actually just another form of object initialization, which was described in Chapter 1, “Type Fundamentals.”
Solution: First decide which type of multidimensional array you need. There are two types in C#: rectangular and jagged. The difference is illustrated in Figures 3.1 and 3.2.
Rectangular arrays are exactly what they sound like: every row is the same length. Here’s an example:
Jagged arrays are actually just arrays of arrays. Here’s an example:
Solution: Rather than importing both types and thus polluting the current context (which would crowd IntelliSense, as one bad side effect), you can alias one or both of them into something shorter for easier use.
The keyword using is also used in the context of the Dispose Pattern (see Chapter 22, “Memory Management”).
?:
)Solution: Use the conditional operator, sometimes called the ternary operator (for its three arguments). For example, (condition?a:b)
is shorthand for if (condition) { do a } else {do b}
.
This program prints the following output:
x is 13
Condition is TRUE
This operator is not limited to just assignments. You can also use it in situations like this:
bool actionSucceeded = CheckIfActionSucceeded();
actionSucceeded ? ReportSucceeded() : ReportFailed();
??
)Solution: The null-coalescing operator can simplify the syntax a bit.
The output is as follows:
o = -1
obj2 = Hello
Solution: Create an extension method using special syntax, as in this example:
A lot of extension methods are defined for you already (mostly for use in LINQ).
Figure 3.3 shows how Visual Studio marks extension methods graphically.
Like operator overloading (See Chapter 2, “Creating Versatile Types”), you should be careful about defining extension methods because they pollute the namespace for all variables of that type.
Solution: Use the new syntax in C# 4.0 to specify parameters with default values.
Default parameter usage can be a contentious issue. Personally, I’d rather have more overloads than use default parameters, but the feature is now here if you want it. Make sure you don’t hurt readability and maintainability with its usage.
Solution: Use the new syntax in C# 4.0 to call methods with named parameters.
Let’s use the same method from the previous section:
//order doesn't matter when they're named
ShowFolders(showFullPath: false, root: @"C:Windows");
Solution: Use the simple helper class Lazy<T>
to wrap the value creation and pass it around as needed. Once the value is created, it is stored so that subsequent accesses use the already created value.
The output is similar to the following:
Solution: Use the Contract
class to add constraints to your methods.
At first glance, these look like assertions—when you are running a debug build, they are. However, they do much more:
• When used with an external tool (called a binary rewriter), they inject code to verify the contract is obeyed, including possibly at the end of methods when needed.
• Emit metadata about the constraint that can then be analyzed by static code analysis tools.
• When run in debug mode, they throw exceptions when contracts are violated.
At the time of this writing, using Code Contracts required an extra download from Microsoft (go to http://research.microsoft.com/en-us/projects/contracts/). Once installed, Code Contracts will add a new tab in your project settings (see Figure 3.4).
With the Visual Studio Team System version, you can also have a static checker that evaluates your code as you write it and notifies you of problems immediately (see Figure 3.5).
Solution: Because interfaces don’t have method bodies, you need to create a surrogate implementation of the interface and add the contracts there. You tie them together using attributes.