Tuesday, June 30, 2015

C# Nutshell Chapter 3 Creating Types in C#

Object Initializers

To simplify object initialization, any accessible fields or properties of an object can
be set via an object initializer directly after construction.

public class Bunny
public string Name;
public bool LikesCarrots;
public bool LikesHumans;
public Bunny () {}
public Bunny (string n) { Name = n; }

// Note parameterless constructors can omit empty parentheses
Bunny b1 = new Bunny { Name="Bo", LikesCarrots=true, LikesHumans=false };
Bunny b2 = new Bunny ("Bo") { LikesCarrots=true, LikesHumans=false };


Properties look like fields from the outside, but internally they contain logic, like
methods do.
A property is declared like a field, but with a get/set block added. Here’s how to
implement CurrentPrice as a property:
public class Stock
decimal currentPrice; // The private "backing" field
public decimal CurrentPrice // The public property
get { return currentPrice; } set { currentPrice = value; }

Const v.s. static readonly

A static readonly field is also advantageous when exposing to
other assemblies a value that might change in a later version.
For instance, suppose assembly X exposes a constant as follows:
public const decimal ProgramVersion = 2.3;
If assembly Y references X and uses this constant, the value 2.3
will be baked into assembly Y when compiled. This means that
if X is later recompiled with the constant set to 2.4, Y will still
use the old value of 2.3 until Y is recompiled. A static
readonly field avoids this problem.
Another way of looking at this is that any value that might
change in the future is not constant by definition, and so should
not be represented as one.


A struct is similar to a class, with the following key differences:
  • • A struct is a value type, whereas a class is a reference type.
  • • A struct does not support inheritance (other than implicitly deriving from object, or more precisely, System.ValueType).

A struct can have all the members a class can, except the following:
  • • A parameterless constructor
  • • A finalizer
  • • Virtual members
public struct Point
int x = 1; // Illegal: cannot initialize field
int y;
public Point() {} // Illegal: cannot have
// parameterless constructor
public Point (int x) {this.x = x;} // Illegal: must assign field y

Class Access Modifiers

Fully accessible. This is the implicit accessibility for members of an enum or
Accessible only within containing assembly or friend assemblies. This is the
default accessibility for non-nested types.
Accessible only within containing type. This is the default accessibility for
members of a class or struct.
Accessible only within containing type or subclasses.

Class2 is accessible from outside its assembly; Class1 is not:

class Class1 {} // Class1 is internal (default)
public class Class2 {}

ClassB exposes field x to other types in the same assembly; ClassA does not:

class ClassA { int x; } // x is private (default)
class ClassB { internal int x; }

Functions within Subclass can call Bar but not Foo:

class BaseClass
void Foo() {} // Foo is private (default)
protected void Bar() {}
class Subclass : BaseClass
void Test1() { Foo(); } // Error - cannot access Foo
void Test2() { Bar(); } // OK

Constructors and Inheritance

A subclass must declare its own constructors. The base class’s constructors are
accessible to the derived class, but are never automatically inherited.

public class Baseclass
public int X;
public Baseclass () { }
public Baseclass (int x) { this.X = x; }
public class Subclass : Baseclass { }
//the following is illegal:
Subclass s = new Subclass (123);

Subclass must hence “redefine” any constructors it wants to expose. In doing so,
however, it can call any of the base class’s constructors with the base keyword:
public class Subclass : Baseclass
public Subclass (int x) : base (x) { }

If a constructor in a subclass omits the base keyword, the base type’s parameterless constructor is implicitly called. If the base class has no accessible parameterless constructor, subclasses are forced to use the base keyword in their constructors.

public class BaseClass
public int X;
public BaseClass() { X = 1; }  //without this, subclass has to use 'base' keyword
public class Subclass : BaseClass
public Subclass() { Console.WriteLine (X); } // 1

Using Interface or Superclass

As a guideline:
• Use classes and subclasses for types that naturally share an implementation.
• Use interfaces for types that have independent implementations.
Consider the following classes:
abstract class Animal {}
abstract class Bird : Animal {}
abstract class Insect : Animal {}
abstract class FlyingCreature : Animal {}
abstract class Carnivore : Animal {}
// Concrete classes:
class Ostrich : Bird {}
class Eagle : Bird, FlyingCreature, Carnivore {} // Illegal
class Bee : Insect, FlyingCreature {} // Illegal
class Flea : Insect, Carnivore {} // Illegal

The Eagle, Bee, and Flea classes do not compile because inheriting from multiple
classes is prohibited. To resolve this, we must convert some of the types to interfaces.
The question then arises, which types? Following our general rule, we could
say that
insects share an implementation, and birds share an implementation, so
they remain classes. In contrast, flying creatures have independent mechanisms
for flying, and carnivores have independent strategies for eating animals, 
so we
would convert FlyingCreature and Carnivore to interfaces:
interface IFlyingCreature {}
interface ICarnivore {}

Enum initialization

It is different from other type initialization. 

public enum BorderSide { Left, Right, Top, Bottom }
BorderSide topSide = BorderSide.Top;
bool isTop = (topSide == BorderSide.Top); // true

Enum Operator Type-safety Issues

Since an enum can be cast to and from its underlying integral type, the actual value
it may have may fall outside the bounds of a legal enum member

BorderSide b = BorderSide.Bottom;
b++; // No errors

An invalid BorderSide would break the following code:

void Draw (BorderSide side)
if (side == BorderSide.Left) {...}
else if (side == BorderSide.Right) {...}
else if (side == BorderSide.Top) {...}
else {...} // Assume BorderSide.Bottom

One solution is to add another else clause:
else if (side == BorderSide.Bottom) ...
else throw new ArgumentException ("Invalid BorderSide: " + side, "side");

Another workaround is to explicitly check an enum value for validity. The static
Enum.IsDefined method does this job:

BorderSide side = (BorderSide) 12345;
Console.WriteLine (Enum.IsDefined (typeof (BorderSide), side)); // False

No comments:

Post a Comment