C#基礎筆記

切套類的使用

There is no strong convention on using nested classes in C#, but I tend to use them when I need to
define a class that is used solely by the outer class or to provide implementations of interfaces.

命名規範

The convention for naming C# fields is to use camel case, such as itemInStock;
For pbulic & static fields, the convention is to use Pascal case, such as ItemInStock;
however, there is an even stronger convention against using public or static fields, preferring properties instead.

Once again, though, let me say that you should use a public static property in preference to a public static field.

初始化

定義時初始化:

class Product {
int itemsInStock = 210;
Supplier productSupplier = new Supplier();
}

或者用constructor初始化
If you define but don’t initialize a field, the field value will be the default value for the field type,
such as 0 for numeric types, null for reference types, and so on.

只讀field

C# supports two slightly different read-only fields, which are available through the const and readonly keywords.

class Product {
public const int UnitsPerCrate = 10;
}

A readonly field can be assigned a value when it is defined or in a class constructor but is otherwise immutable.
Allowing a readonly field to be modified during object construction is a useful feature. It means that you can create a per-instance constant value, which depends on the way in which your object was constructed.

隱藏基類Fields

If you want to define a field with the same name as a field in your base class, you can do so by using the new keyword.

class Product {
    protected int myField;
}
class DerivedProduct : Product {
    private new string myField;
    public int MyIntProperty {
        get { return base.myField; }
        set { base.myField = value; }
    }
    public string MyStringProperty {
        get { return this.myField; }
        set { this.myField = value; }
    }
}

In short, if an object created from a class that hides a base field is upcast to a base type, then the hidden field will be the one that is accessed by other classes. If the same object is referred to by its declared type, then the new field is access.

using System;
class BaseProduct {
    public string myField = "Base Value";
}
class DerivedProduct : BaseProduct {
    public new string myField = "Derived Value";
}
class Listing_17 {
    static void Main(string[] args) {
        // create an object from the derived class
        DerivedProduct derivedObject = new DerivedProduct();
        // upcast the objet to the base type
        BaseProduct upcastObject = derivedObject;
        // print out the field values
        Console.WriteLine("Derived Field: {0}", derivedObject.myField);//output: Derived Field: Derived Value
        Console.WriteLine("Base Field: {0}", upcastObject.myField);//output: Base Field: Base Value
        // wait for input before exiting
        System.Console.WriteLine("Press enter to finish");
        System.Console.ReadLine();
    }
}

You can see that upcasting an object has the effect of “unhiding” the field in the base class. When hiding fields, you must be confident of how objects created from your class are going to be treated, since upcasting changes the way that they behave.

使用volatile關鍵字

The volatile modifier is applied to fields that may be accessed by multiple threads without the use of the lock keyword or other synchronization mechanisms. This keyword prevents some compiler optimizations that assume access by a single thread. You can learn more about synchronization when we explore parallel and multithreaded programming techniques later in the book.

Properties/Indexers/Fields

Properties are class members that allow you to expose a characteristic of an object.
Indexers are members that allow you to treat a class as though it were an array, using the same index syntax ([])
Properties and indexers both allow you to control access to the fields in your class and give you more control than exposing a field directly. As you’ll see when we get into the detail, properties and indexers are very flexible; in fact, they are so rich that they can sometimes blur the distinction between fields and methods.

Creating a Field-Backed Property

The standard way of using a property is to use it as a mediator to a field, typically to ensure that other classes cannot set the field to a value that would create an exceptional state for your class.

Using a Property to Validate Field Values

class Product {
    private int itemsInStock;
    public double PricePerItem;
    public int ItemsInStock{
    get{return itemsInStock;}
    set{
        if(value >=0){itemsInStock = value;}
        else{throw new ArgumentOutOfRangeException();}
    }
}

Creating an Automatically Implemented Property

There are no bodies for the accessors in an automatically implemented property. You just use the get and set keywords, followed by a semicolon. Most importantly, there is no field either.

class Product {
    public int ItemsInStock {get;set;}
}

Creating an Asymmetric(不對稱的) Property

You don’t have to implement both accessors in a property. If you omit the set accessor, you create a read-only property, and if you omit the get accessor, you create a write-only property.

Creating a Computed Property

Properties are very flexible and need not be used just to mediate access to fields. They can also be used to access values that are computed on the fly.

class Product {
    public int ItemsInStock { get; set; }
    public double PricePerItem { get; set; }
    public double TotalValueOfStock {
        get { return ItemsInStock * PricePerItem;}
    }
}

there are two automatically implemented properties, ItemsInStock and PricePerItem, and a read-only computed property called TotalValueOfStock, which uses the other two properties to return a result.

Mapping a Property Type to a Field Type

You can use a property of one type to mediate access to a field of a different type by implementing the conversion between types in the accessors.
such as maps a string property to a double field.

Using Access Modifiers to the get and set accessors

class Product {
    public int ItemsInStock {
        private get;
        set;
    }
}

Using Other Modifiers: virtual, overrides, abstract, sealed, static…

Indexers allow you to expose the data contained by an object using array notation([]).

class Product{
    private string[] productNames = new string[]{"orange", "apple", "pear", "banana", "cherry"};
    public string this[int index]{
        get{return productNames[index];}
        set{productNames[index] = value;}
    }
}
Produce p = new Product();
p[0] = "test";

You can see from the Main method in the previous class that using an indexer is just like using an array. The listing demonstrates the most common use of indexers, known as the collection-backed indexer.

Extension Methods

Extension methods allow you to seemingly add methods to classes without modifying them. I say
seemingly, because extension methods are a clever feature that makes it appear as though a method has been added to a class when it hasn’t really.

Extension methods are most useful when you don’t have access to the source code of the class you want to work with.

Extension methods must be static and can exist only inside a static class.
Unlike partial methods, the name of the class that contains the extension methods doesn’t matter.
The first parameter in an extension method must have the this modifier, and the type of the
parameter is the type on which the extension method will operate.

class Person {
  public string Name { get; set; }
  public int Age { get; set; }
  public string City { get; set; }
  public Person(string name, int age, string city) {
    Name = name; Age = age; City = city;
  }
}
static class ExtensionMethods {
  public static void PrintInformation(this Person p) {
    Console.WriteLine("--- Person ---");
    Console.WriteLine("Name: {0}", p.Name);
    Console.WriteLine("Age: {0}", p.Age);
    Console.WriteLine("City: {0}", p.City);
  }
}
Person p = new Person("Adam Freeman", 38, "London");
p.PrintInformation();// call the extension method

You don’t need to provide a value for the first parameter in an extension method (the one modified
with this). It is implicit from the object that you have called the method on. Inside an extension method,
you can refer to this parameter to access the members of the object. Extension methods don’t have any
special access to the members of an object, so you can only use those members to which you have access. This will typically be those that are modified using the public or internal keywords.
This is what I meant when I said that extension methods seem to extend classes. In fact, we have
defined an entirely method in an entirely separate class, but the magic of the C# compiler allows us to
use it as though it were part of the original Person class.

Delegate

A delegate is a special C# type that represents a method signature.

**AccessModifier delegate ResultType DelegateName(parameters);
public delegate int PerformCalc(int x, int y);**

DelegateName is equivalent to the class name. It is important to bear in mind that when we define a new delegate, we are defining a new type.
You can define a new delegate type in the same places as you can create a new class - in a namespace, class, or struct.
Once we have defined a new delegate type, we can create an instance of it and initialize it with a value.
You can also use generic types with delegates. If we wanted a generic version of the delegate type:

public delegate T PerformCalc<T>(T x, T y);
class Calculator {
    PerformCalc<int> perfCalc;

Using Delegates for Callbacks

You can use delegates to create callbacks, where one object is notified when something of interest happens in another object.

delegate void NotifyCalculation(int x, int y, int result);
class Calculator {
  NotifyCalculation calcListener;
  public Calculator(NotifyCalculation listener) {
    calcListener = listener;
  }
  public int CalculateProduct(int num1, int num2) {
    int result = num1 * num2;
    calcListener(num1, num2, result);// notify the delegate that we have performed a calc
    return result;
  }
}

class CalculationListener {
  public static void CalculationPrinter(int x, int y, int result) {
    Console.WriteLine("Calculation Notification: {0} x {1} = {2}", x, y, result);
  }
}

Calculator calc = new Calculator(CalculationListener.CalculationPrinter);
calc.CalculateProduct(10, 20);//Calculation Notification: 10 x 20 = 200
calc.CalculateProduct(2, 3);//Calculation Notification: 2 x 3 = 6

When performing callbacks, you will often need to cater for multiple listeners, rather than the single
listener. The delegate type uses custom + and – operators that let you combine several method references together into a single delegate and invoke them in one go, known as
multicasting.

delegate void NotifyCalculation(int x, int y, int result);
class Calculator {
    NotifyCalculation calcListener;
    public void AddListener(NotifyCalculation listener) {
        calcListener += listener;
    }
    public void RemoveListener(NotifyCalculation listener) {
        calcListener -= listener;
    }
    public int CalculateProduct(int num1, int num2) {
        int result = num1 * num2;
        calcListener(num1, num2, result);// notify the delegate that we have performed a calc
        return result;
    }
}

class CalculationListener {
    private string idString;
    public CalculationListener(string id) {
        idString = id;
    }
    public void CalculationPrinter(int x, int y, int result) {
        Console.WriteLine("{0}: Notification: {1} x {2} = {3}", idString, x, y, result);
    }
}
class AlternateListener {
    public static void CalculationCallback(int x, int y, int result) {
        Console.WriteLine("Callback: {0} x {1} = {2}", x, y, result);
    }
}
// create a new Calculator
Calculator calc = new Calculator();

// create and add listeners
calc.AddListener(new CalculationListener("List1").CalculationPrinter);
calc.AddListener(new CalculationListener("List2").CalculationPrinter);
calc.AddListener(AlternateListener.CalculationCallback);

// perform a calculation
calc.CalculateProduct(10, 20);

// remove a listener
calc.RemoveListener(AlternateListener.CalculationCallback);

// perform a calculation
calc.CalculateProduct(10, 30);

Interrogating Delegates代理查詢

The base type for all delegates is System.Delegate, and we can use the members of this class to find out
which methods a delegate will invoke on our behalf.

delegate int PerformCalc(int x, int y);
class Calculator {
    public int CalculateSum(int x, int y) {
        return x + y;
    }
}
class AlternateCalculator {
    public int CalculateProduct(int x, int y) {
        return x * y;
    }
}
// create a delegate variable
PerformCalc del = new Calculator().CalculateSum;
// combine with another method
del += new AlternateCalculator().CalculateProduct;
// Interrogate the delegate
Delegate[] inlist = del.GetInvocationList();
foreach (Delegate d in inlist) {
    Console.WriteLine("Target: {0}", d.Target);
    Console.WriteLine("Method: {0}", d.Method);
}

Using Events

Events are specialized delegates designed to simplify the callback model.
There can be a problem when you use a delegate type as a field, where one object interferes with another.

1). The first step in defining an event is to derive a class from the System.EventArgs class, which contains
properties and fields to contain any custom data that you want to pass when you invoke the event
delegate; the name of this class should end with EventArgs.

You can include any fields and properties that you need to express information about your event,
but you should make sure that the fields cannot be modified. This is because the same EventArgs object
will be passed to each of the subscribers of your event, and a poorly or maliciously coded recipient could
change the information that subsequent listeners receive.

class CalculationEventArgs : EventArgs {
    private int x, y, result;
    public CalculationEventArgs(int num1, int num2, int resultVal) {
        x = num1;
        y = num2;
        result = resultVal;
    }
    public int X {
        get { return x; }
    }
    public int Y {
        get { return y; }
    }
    public int Result {
        get { return result; }
    }
}

2)The next step is to define an event in your class. To define the event using the generic EventHandler delegate.
The convention is that the name of the event should end with the word Event.
The convention dictates that you put the code to invoke your event delegate in a method whose name starts with On concatenated with the event name, less the word event.
So, for example, since we have defined an event called CalculationPerformedEvent, the method would be called OnCalculationPerformed.
This method should make a copy of the event to avoid a race condition and ensure that the event has subscribers by ensuring that the event field is not null.

class Calculator {
    public event EventHandler<CalculationEventArgs> CalculationPerformedEvent;
    public int CalculateProduct(int num1, int num2) {
        int result = num1 * num2;
        OnCalculationPerformed(new CalculationEventArgs(num1, num2, result));// publish the event
        return result;
    }
    private void OnCalculationPerformed(CalculationEventArgs args) {
        // make a copy of the event
        EventHandler<CalculationEventArgs> handler = CalculationPerformedEvent;   
        if (handler != null) {// check to see we have subscribers
            handler(this, args);
        }
    }
}

3)Subscribing to events is just like using a delegate, with the exception that the subscriber is limited to using the += and -= operators.

static void HandleEvent(object sender, CalculationEventArgs e) {
    Console.WriteLine("Good Class: {0} x {1} = {2}", e.X, e.Y, e.Result);
}
// create a new instance of the Calculator class
Calculator calc = new Calculator();
// subscribe to the event in the calaculator class
calc.CalculationPerformedEvent += HandleEvent;//handle event method should not publically accessible;
// perform a calculation
calc.CalculateProduct(20, 72);

You must ensure that the method you are going to use to handle events is not publically accessible;
otherwise, you are still liable to encounter problems with other classes.

Creating Events Without Custom Data

If you don’t need to pass custom data as part of your event, then you can use EventArgs directly.
There is no need to define a custom delegate if you don’t need custom data. When defining the
event, you simply use the EventHandler type, as follows:
public event EventHandler CalculationPerformedEvent;
The event still requires two arguments to invoke it, but you can use the static EventArgs.Empty
property to get a reference to a ready-made EventArgs instance that has no custom data.

class Calculator {
    public event EventHandler CalculationPerformedEvent;
    public int CalculateProduct(int num1, int num2) {
        int result = num1 * num2;
        OnCalculationPerformed();
        return result;
    }
    private void OnCalculationPerformed() {
        // make a copy of the event
        EventHandler handler = CalculationPerformedEvent;
        // check to see we have subscribers
        if (handler != null) {
            handler(this, EventArgs.Empty);
        }
    }
}
static void HandleEvent(object sender, EventArgs e) {
    Console.WriteLine("Event Received");
}
// create a new instance of the Calculator class
Calculator calc = new Calculator();
// subscribe to the event in the calaculator class
calc.CalculationPerformedEvent += HandleEvent;
calc.CalculateProduct(20, 72);

You should not use the virtual modifier on events. If you do, it is possible that your events will not
be delivered properly. If you want to override an event from a base class, then you should mark the OnXXX method as virtual and override the method in the derived class.

Using Action Delegates

The Func and Action classes are special types that allow you to use delegates without having to specify a custom delegate type. They are used throughout the .NET Framework.

Action delegates encapsulate methods that do not return results.
In other words, they can be used only with methods that are defined with the void keyword.
The simplest Action delegate is the System.Action class, which is used for methods that have no parameters (and no results).

class Calculator {
    public void CalculateProduct() {
        int result = 10 * 20;
        Console.WriteLine("Result: {0}", result);
    }
}
Calculator calc = new Calculator();
Action act = calc.CalculateProduct;// create an action and assign a method
act();// invoke the method via the Action

We didn’t have to define a custom delegate in this example. The System.Action type handled
everything for us.

There are 17 different Action implementations available.

//uses two parameters
Action<int, int> act = calc.CalculateProduct;// create an action and assign a method
act(10, 20);// invoke the method via the Action

Using Func Delegates

System.Func delegates are just like Action delegates, except that they can return results.
The simplest Func implementation has no parameters.

class Calculator {
    public int CalculateProduct() {
        return 10 * 20;
    }
}
Func<int> act = calc.CalculateProduct;
int result = act();// invoke the method via the Action

Just like Action, there are 17 Func implementations with an increasing number of parameters, each of which can be of a different type.

class Calculator {
    public int CalculateProduct(int x, int y) {
        return x * y;
    }
}
//Func with two int parameters
Func<int, int, int> act = calc.CalculateProduct;// create a Func and assign a method
int result = act(10, 20);// invoke the method via the Action

Anonymous Methods

All of the examples so far in this chapter have used named methods, that is, methods that exist in classes
and have a method identifier. C# also supports anonymous methods, which allow you to implement a
delegate without defining a method.

Calculator calc = new Calculator();
calc.CalculationPerformedEvent += delegate(object sender, CalculationEventArgs e){
     Console.WriteLine("Anonymous Calc: {0} x {1} = {2}, e.X, e.Y, e.Result);
}
calc.CalculateProduce(20, 40);

The anonymous meothod has a number of similarities to a named method.

delegate(object sender, CalculationEventArgs e){
     Console.WriteLine("Anonymous Calc: {0} x {1} = {2}, e.X, e.Y, e.Result);
}

There is no method identifier(no method name), and use the delegate keyword.
Anonymous methods work very well with Func and Action delegates, allowing you to define a
delegate without needing to create a delegate type or implement a named method. When implementing
a delegate that returns a result, simply use the return keyword as you would for a named method.

Func<int, int, int> productFunction = delegate(int x, int y) {
    return x * y;
};

Capturing Outer Variables

Anonymous methods do more than reduce the number of methods in your class; they are also able to
access the local variables that are defined in the containing methods, known as outer variables.
An anonymous method that captures variables is called a closure.

static void Main(string[] args) {
    int calculationCount = 0;// define a local variable
    Func<int, int, int> productFunc = delegate(int x, int y) {// define and implement a Func
        calculationCount++;// increment the outer variables
        return x * y;
    };
}

Captured variables are evaluated when the delegate is invoked, which means that you should take
care when making assumptions about the value of a variable.

Lambda expressions have largely replaced anonymous methods since they were introduced in C# 3.0.
They have much the same functionality as an anonymous method but are slightly more convenient to use.

// implement an anonymous method that multiplies ints
Func<int, int, int> anonFunc = delegate(int x, int y) {
    return x * y;
};
// do the same thing with a lambda expression
Func<int, int, int> lambaFunc = (x, y) => {
    return x * y;
};
(x, y)                  =>             { return x * y; };
Parameter     lambda OP      Code Statements
(x, y)                  =>             x * y; //can omit the braces and the return keyword if there is a single operation in a lambda expression

The C# compiler is pretty good at inferring the types of parameters for lambda expressions,
but on occasion the compiler won’t be able to work it out, and you will you need to explicitly tell the compiler what they are.

Ex: (int x, int y) => x * y;

When to use interfaces and base classes

Base classes allow you to create shared behavior in derived class,
which is useful when all the classes that you want to have the same features are related to one another.
Interfaces allow you to create common behavior across classes that do not share common ancestors,
meaning that you can upcast any object whose class implements a given interface and handle them using the same code.

The convention for naming interfaces in C# is that follow Pascal case and start with the letter I.

public interface IBasicCalculator{//defining an interface
    int CalculateSum(int x, int y);
    int CalculateProduce(int x, int y);
}
class Calculator : IBasicCalculator{//implementing an interface
    int CalculateSum(int x, int y){
        return x + y;
    }
    int CalculateProduce(int x, int y){
        return x * y;
    }
}
//using an interface
IBasicCalculator calc = new Calculator();// create an object and upcast it to the interface type
int sumresult = calc.CalculateSum(100, 100);// perform some calculations using the interface members
int productresult = calc.CalculateProduct(100, 100);

Specifying Interface Members

An interface can specify methods, properties, events, and indexers for classes to implement.
All interface member specifications are implicitly abstract, implicitly virtual, and implicitly public.
You do not need to use the override keyword to implement a method specified by an interface.

Interfaces can specify properties, obliging(迫使做) a class that implements the interface to provide implementations for one or both accessors.
You can choose to specify only one of the accessors, but whatever accessors you specify will have to be implemented by a class that implements the interface itself.
Interfaces can specify events; the specification of an event in an interface and the corresponding implementation in a class are surprisingly similar.
The remaining kind of member you can implement in an interface is an indexer. You can choose to specify one or both of the accessors.

public interface IBasicCalculator {
    int this[int x, int y] { get; set; }//indexer
    event EventHandler<EventArgs> CalculationPerformedEvent;//event,do not use an access modifier in the specification
    int CalculationsPerformedCounter { get; set; }//property
    int CalculateSum(int x, int y);//method
}
public class Calculator : IBasicCalculator {
    public event EventHandler<EventArgs> CalculationPerformedEvent;//The implementation of the event in the class is the same as the specification in the interface, with the addition of the public access modifier.
    private int calcCounter = 0;
    public int CalculationsPerformedCounter {//Properties that are implemented for interfaces must be public.
        get {  return calcCounter; }
        set { calcCounter = value; }
    }
    public int CalculateSum(int x, int y) {//when a class implements an interface that specifies a method, the method implementation must be public.
        CalculationsPerformedCounter++;    
        CalculationPerformedEvent(this, EventArgs.Empty);// invoke the event
        return x + y;
    }
    //This class uses the getter to perform calculations and will throw an exception if the setter is used.
    public int this[int x, int y] {//indexer
        get { return x + y;}
        set { throw new NotImplementedException();}
    }
}

Deriving Interfaces

Interfaces can be derived from other interfaces.
A derived interface inherits all the member specifications of the base interface. A class that
implements IDerivedCalculator must implement the CalculateProduct method it specifies as well as
the CalculateSum method specified by the base interface.

Deriving from Multiple Base Interfaces

Unlike classes, interfaces can have more than one base interface.
Classes can be derived from only a single class, but they can implement multiple interfaces.
When a class implements an interface that has multiple base interfaces, objects created from that
class can be upcast to any of the base interface types. When this is done, only the members specified in
the base interface can be accessed.

interface IProductCalculator {
    int CalculateProduct(int x, int y);
}
interface ISumCalculator {
    int CalculateSum(int x, int y);
}
interface ISubtractionCalculator {
    int CalculateSubtraction(int x, int y);
}
interface ICombinedCalculator : IProductCalculator, ISumCalculator, ISubtractionCalculator {
    int CalculateDivision(int x, int y);
}
class Calculator : ICombinedCalculator {
    public int CalculateDivision(int x, int y) {
        return x / y;
    }
    public int CalculateProduct(int x, int y) {
        return x * y;
    }
    public int CalculateSum(int x, int y) {
        return x + y;
    }
    public int CalculateSubtraction(int x, int y) {
        return x - y;
    }
}
ICombinedCalculator calc = new Calculator();// create an object and upcast it to the combined interface
IProductCalculator prodCalc = calc;// upcast to the base interfaces and call the method that each defines
int prodResult = prodCalc.CalculateProduct(10, 10);
ISumCalculator sumCalc = calc;
int calcResult = sumCalc.CalculateSum(10, 10);
ISubtractionCalculator subCalc = calc;
int subResult = subCalc.CalculateSubtraction(10, 2);
//You can also explicitly cast from one base interface to another (but only if you are sure that the object you are casting implements both interfaces),
prodCalc = (IProductCalculator)subCalc;// explicitly cast from one base interface to another

Defining a Partial Interface

Interfaces can be modified with the partial keyword and defined in several places. Just as with partial classes
Classes can implement an interface in addition to(除…之外) being derived from a base class.類可以從基類派生的同時實現接口。

Explicitly Implementing an Interface
interface ISumCalculator {
    int PerformCalculation(int x, int y);
}
interface IProductCalculator {
    int PerformCalculation(int x, int y);
}
class Calculator : ISumCalculator, IProductCalculator {
    public int PerformCalculation(int x, int y) {
        // ???The problem is knowing which interface the PerformCalculation method should be implemented for.
    }
}
To get around this(爲了解決這個問題), you can use explicit interface implementations, where you specify which interface a member implementation relates to.
class Calculator : ISumCalculator, IProductCalculator {
    //When you explicitly implement a member specified by an interface, you prefix the implementation with the name of the interface it relates to
    int ISumCalculator.PerformCalculation(int x, int y) {
        Console.WriteLine("ISumCalculator.PerformCalculation was called");
        return x + y;
    }
    int IProductCalculator.PerformCalculation(int x, int y) {
        Console.WriteLine("IProductCalculator.PerformCalculation was called");
        return x * y;
    }
}

Calculator calc = new Calculator();// create an instance of the Calculator object
IProductCalculator pcalc = calc;// upcast to IProductCalculator and call the method
int productResult = pcalc.PerformCalculation(10, 10);
ISumCalculator scalc = calc;// upcast to ISumCalculator and call the method
int sumResult = scalc.PerformCalculation(10, 10);

The explicit implementations of an interface member are available only when an object is upcast to
that interface type. This means, for example, that you cannot call the PerformCalculation method on
objects created from the class until they are upcast, and this means that the Calculator
class appears to have no members until it is upcast to one of the explicitly implemented interfaces.

Implementing an Interface in an Abstract Class

An abstract class can implement an interface but force derived classes to provide an implementation of the members that the interface specifies.

interface ICalculator {
    int CalculateSum(int x, int y);
    int CalculateProduct(int x, int y);
}
abstract class AbstractCalculator : ICalculator {
    //To force a derived class to provide an implementation for an interface member, you simple restate(重申;再次聲明) the member specification, 
    //adding the public access modifier and the abstract keyword.
    public abstract int CalculateSum(int x, int y);
    public int CalculateProduct(int x, int y) {
        return x * y;
    }
}
class Calculator : AbstractCalculator {
    public override int CalculateSum(int x, int y) {
        return x + y;
    }
}

Differences Between Structs and Classes

C# struct feature allows you to create your own value types. Structs can use
many of the same features as classes but have some special constraints and demonstrate value-type behavior.
Structs are value types, so when we use the new keyword, we create a struct value.
Structs can have constructors and fields.
Structs can contain the same set of members as classes, including methods, properties, events, indexers, and custom operators.
Structs can implement interfaces and, like classes, can implement more than one interface.

Structs are implicitly derived from the System.ValueType class and are also implicitly sealed. This means
that a struct cannot be derived from another struct (or class), and a struct cannot act as the base for
another struct (or class).Because there can be no base or derived structs, the C# compiler will report an error if you use the virtual, override, sealed, or abstract keywords on methods and other members in a struct.

When defining fields in a constructor, you may not initialize a field with a value.

public struct Product {
    public int CasesInStock = 20;
}

This will not compile. you must do like this:

public struct Product {
    public int CasesInStock;
}
Product p = new Product();
p.CasesInStock = 20;

All structs have an implicit parameterless constructor that cannot be override by the struct. If you create
a struct value using the default constructor, all of the fields and properties will be initialized to the
default value for their type.

If you define a custom constructor, you must initialize all the fields and properties(所有的都要初始化) that your struct defines. Implementing a custom constructor doesn’t stop the default constructor being used.

public struct Product {
    public int CasesInStock;
    public int ItemsPerCase;
    public Product(int cases, int items) {
        CasesInStock = cases;
        ItemsPerCase = items;
    }
}
Product prod = new Product(20, 20);//custom constructor 
Product prod = new Product();//default constructor

The biggest difference between a struct and a class comes when you copy a struct. Since a struct is a value type, a
new and distinct copy of the struct is created.
Structs are value types, but you need to be careful that you understand the mix of behaviors when you use
reference types as fields.

Using Underlying Types and Numeric Values

When you specify constant values in an enum, the values are assigned a number. By default, this starts at
zero and is incremented for each constant.

public enum PaintColor {
    Black = 10,
    Red,
    Green = 15,
    Silver
}

you don’t have to assign numeric values to all the constants. Values
are assigned automatically by incrementing the previous value so that the numeric value of Red is 11 and
the numeric value of Silver will be 16.

Combining Enum Values

You can combine the values of an enum by using the C# logical operators
To do this, you create an enum where the numeric values assigned to each constant increase in
powers of two.

[Flags]
public enum CarOptions {
    AlloyWheels = 1,
    CDPlayer = 2,
    SatNav = 4,
    Bluetooth = 8
}//You should apply the Flags attribute when you define an enum like this.
// combine two of the values together
CarOptions ops = CarOptions.AlloyWheels | CarOptions.SatNav;
// test to see if the combined value contains SatNav
bool hasSatNav = (ops & CarOptions.SatNav) == CarOptions.SatNav;

Using Array Initializers

// define, initialize and populate a string array in one go
string[] names1 = { "oranges", "apples", "guava" };
// define, initialize and populate a string array in one go
string[] names2 = new string[] { "oranges", "apples", "guava" };
// define, initialize and populate a string array in one go
string[] names3 = new string[3] { "oranges", "apples", "guava" };
// create and initialize a new array
Product[] prods = {
    new Product("oranges"),
    new Product("apples"),
    new Product("guava")
};

Using System.Array Members

C# arrays are implemented using the System.Array class.

Sorting Arrays
You can sort the contents of an array using the static System.Array.Sort method.

// define and populate the array
string[] names = { "oranges", "apples", "guava", "peaches", "bananas", "grapes" };
Array.Sort(names);// sort the array

Processing All of the Elements in an Array
The static Array.ForEach method lets you specify an Action delegate that will be applied to each element
in the array in turn.

static void printItem(string param) {
    Console.WriteLine("Item: {0}", param);
}
// define and populate the array
string[] names = { "oranges", "apples", "guava", "peaches", "bananas", "grapes" };
// define an Action delegate
Action<string> act = new Action<string>(printItem);
// enumerate the contents of the array using the delegate
Array.ForEach(names, act);

You can use anonymous methods and delegates with the ForEach method.
However, if you end up using anonymous methods with the
Array.ForEach method, you should consider using a regular foreach loop.

// define and populate the array
string[] names = { "oranges", "apples", "guava", "peaches", "bananas", "grapes" };
// enumerate the contents of the array using the delegate
Array.ForEach(names, s => {
    Console.WriteLine("Item: {0}", s);
});

Resizing and Copying an Array
Array.Copy

// define and populate an array
string[] names = { "oranges", "apples", "guava", "peaches", "bananas", "grapes" };
// create a larger array
string[] biggerNames = new string[names.Length + 2];
// copy the data from the original array to the new array
Array.Copy(names, biggerNames, names.Length);
// assign the new array to the old array variable
names = biggerNames;

Array.Resize

// define and populate an array
string[] names = { "oranges", "apples", "guava", "peaches", "bananas", "grapes" };
// resize the array
Array.Resize(ref names, 8);

Finding Items in an Array
Array.Find & System.Predicate delegate

static bool CheckString(string s) {
    return s.StartsWith("p");
}
// define and populate an array
string[] names = { "oranges", "apples", "guava", "peaches", "bananas", "grapes" };
// define the predicate
Predicate<string> pred = new Predicate<string>(CheckString);
// search for a match
string match = Array.Find(names, pred);
//lambda expression
string match = Array.Find(names, s => s.StartsWith("p"));

Array.FindAll

// define and populate an array including some null values
string[] names = { "oranges", "apples", null, "guava", "peaches", null};
// shrink the array by finding all of the items which are not null
string[] shrinkNames = Array.FindAll(names, s => s != null);

Using Arrays with LINQ
use LINQ to shrink an array and remove null values

// define and populate an array including some null values
string[] names = { "oranges", "apples", null, "guava", "peaches", null };
IEnumerable<string> filtered = from s in names
                                               where s != null
                                               select s;
string[] shrinkNames = filtered.ToArray();

Using Arrays as Collections

C# arrays implicitly implement interfaces from the Sytem.Collections and System.Collections.Generic namespaces.

// define and populate an array
string[] names = { "oranges", "apples", "guava", "peaches", "bananas", "grapes" };
// implicitly cast the array to an IList<T>
IList<string> ilist = names;
// access the array through the IList<T> members
int index = ilist.IndexOf("apples");

Using Multidimensional Arrays
Using Rectangular Arrays

int[,] rectArray = new int[3, 4];
// define and populate a rectangular array of strings
string[,] namesArray = {
    {"apples", "oranges", "grapes", "pears"},
    {"green", "orange", "red", "green"}
};

int dim0Len = namesArray.GetLength(0);
int dim1Len = namesArray.GetLength(1);
for (int row = 0; row < dim0Len; row++) {
    for (int column = 0; column < dim1Len; column++) {
        Console.WriteLine("Row: {0}, Col: {1}, Value: {2}",
        row, column, namesArray[row, column]);
    }
}
foreach (string s in namesArray) {
    Console.WriteLine("Item: {0}", s);
}

Using Jagged(參差不齊的;鋸齒狀的) Arrays
You can also create jagged arrays that have irregular(不規則的,不對稱的) capacity.
Jagged arrays are also known as arrays of arrays

string[][] jaggedArray = new string[3][];
jaggedArray[0] = new string[2];
jaggedArray[1] = new string[1];
jaggedArray[2] = new string[3];

string[][] jaggedArray = new string[3][];
jaggedArray[0] = new string[] {"apples", "oranges"};
jaggedArray[1] = new string[] {"bananas"};
jaggedArray[2] = new string[] { "guavas", "pears", "cherries" };

foreach (string[] outer in jaggedArray) {
    foreach (string s in outer) {
        Console.WriteLine("Item: {0}", s);
    }
}
for (int i = 0; i < jaggedArray.Length; i++) {
    for (int j = 0; j < jaggedArray[i].Length; j++) {
        Console.WriteLine("Row: {0}, Col: {1}, Value: {2}", i, j, jaggedArray[i][j]);
    }
}

Exception

In C#, all exceptions are runtime exceptions, meaning that methods do not have
to declare the exceptions they will throw (for example, there is no equivalent to the Java throws keyword in method definitions).
The root for all exceptions in .NET is the System.Exception class, so you can catch every kind of
exception by specifying this as the type in the catch clause.
if you define a catch clause using System.Exception, it must be the last clause in the try statement, because it will always make a match for an exception and any subsequent clauses will not be checked and used.
A catch clause for System.Exception is most frequently used as a backstop(守場員) to ensure that all
exceptions are handled, preventing the exception from being pushed up the call stack and potentially
triggering the default exception handler.

The identifier for the exception in a catch clause is optional, but if you omit the identifier, then you
are unable to access the members of the exception that is being handled.

try {
// statements likely to cause exceptions
} catch (NullReferenceException ex) {
    Console.WriteLine("Message: {0}", ex.Message);
} catch (ArgumentOutOfRangeException) {
// code which handles an ArgumentOutOfRangeException
} catch{//general catch clause
// code which handles all exception types not handled in the clauses above
//general clauses are rarely used, because they don’t allow you to access the exception that is being thrown, which is a severe limitation. 
//A more common solution is to use a specific catch clause for the System.Exception type;
}

Omitting catch Clauses

you can define try statements that don’t have any catch clauses at all.

try {
// statements likely to cause exceptions
} finally {
// finally statements go here...
}

Any exceptions caused by the code statements enclosed by the try statement will go unhandled,
and the runtime will have to search further up the call stack.This is a very uncommon use of the try statement, which usually includes at least one catch clause.

Nesting try Statements

You can define one try statement so that it is enclosed by another.

try {
    // define a loval variable
    string myLocalVar = null;
    // try to so something with the local variable
    Console.WriteLine("First letter: {0}", myLocalVar[0]);
} catch (Exception ex) {
    Console.WriteLine("Type: {0}", ex.GetType());
    Console.WriteLine("Message: {0}", ex.Message);
    Console.WriteLine("Stack: {0}", ex.StackTrace);
}

Type: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Stack: at Listing 10.Main(String[] args) in C:\Listing 10\Listing 10.cs:line 12

Using finally Clauses

The way that control can jump around in a try statement can cause problems for some programs. This is
especially true if you need to release resources or reset the state of your program whether an exception is
thrown and handled. You can do this by adding a finally clause to your try statement.

A try statement can have only one finally clause, and it must be the last clause defined in the
statement.The statements in the finally clause are executed, even though no exception has been thrown.

Throwing Exceptions

Handling exception is only half of the story. You also need to throw them in your code when errors
occur. This is done using the throw keyword.

// define a string array
string[] array = { "orange", "apple", "pear" };
try {
    // make a call to the GetStringLength method
    int result = GetStringLength(array, 2);
    Console.WriteLine("Result: {0}", result);
    // make a call that will cause an exception
    result = GetStringLength(array, 100);
} catch (ArgumentOutOfRangeException ex) {
    Console.WriteLine(ex.ToString());
}
static int GetStringLength(string[] array, int index) {
    if (index < array.Length) {
        return array[index].Length;
    } else {
        // create a new exception
        ArgumentOutOfRangeException ex = new ArgumentOutOfRangeException( "index", index, "Index is greather than array length");
        // throw the exception
        throw ex;
    }
}

Rethrowing Exceptions

You can use the throw keyword on its own in a catch clause to rethrow the exception that caused the
catch clause to be invoked. This is useful if you want to log the exception, but otherwise rely on the
runtime to find another clause to handle the exception.
You can also rethrow an exception after adding additional data. Every exception class inherits the
Data property from the System.Exception class, which returns an IDictionary in which you can place
name/value pairs.

try {
    try {
        throw new NullReferenceException();  // throw an exception
    } catch (NullReferenceException ex) {     
        IDictionary d = ex.Data;// get the dictionary from the exception
        // add some additional information to the exception
        d.Add("Additional Information", "This is some helpful state info");
        d.Add("More Information", "This is some extra helpful state info");

        throw; // rethrow the exception
    }
} catch (NullReferenceException ex) {
    // handle the exception
    Console.WriteLine("Outer try statement - Exception handled: {0}", ex.GetType());
    IDictionary d = ex.Data;//get the additional info
    foreach(object o in d.Keys){
        Console.WriteLine("Info: {0} = {1}", o, d[o]);
    }
}

Creating and Throwing Custom Exceptions

You can easily do this by deriving from System.Exception.

class CustomException : Exception {
    public CustomException()
    : base() {
    }
    public CustomException(string message)
    : base(message) {
    }
    public CustomException(string message, Exception inner)
    : base(message, inner) {
    }
}

You can define additional
constructors as required, but you should always include the three that are shown in above
You don’t need to use the type parameter notation when you define a constructor for a generic
class. Even though the class is GenericStack, the constructor is named GenericStack

Using Generic Types

class GenericStack<T>{
    T[] dataArray = new T[10];
    int currentPos = 0;

    public GenericStack(int capacity) {
        dataArray = new T[capacity];
        currentPos = 0;
    }

    public void Push(T value) {
        dataArray[currentPos++] = value;
    }
    public T Pop() {
        return dataArray[--currentPos];
    }
}
GenericStack<int> intStack = new GenericStack<int>(10);

Defining Multiple Parameter Types

You are not limited to one parameterized type when you define a generic class. You can create as many
as you need.

class KeyStack<TKey, TVal> {
    TKey[] keysArray = new TKey[5];
    TVal[] valsArray = new TVal[5];
    int currentPos = 0;
    public void Push(TKey newKey, TVal newVal) {
        keysArray[currentPos] = newKey;
        valsArray[currentPos] = newVal;
        currentPos++;
    }
    public Tuple<TKey, TVal> Pop() {
        currentPos -= 1;
        return new Tuple<TKey, TVal>(keysArray[currentPos], valsArray[currentPos]);
    }
}
KeyStack<string, int> stack = new KeyStack<string, int>();

Constraining Parameterized Types

class GenericStack<T> where T : IComparable<T> {
    T[] dataArray = new T[10];
    int currentPos = 0;
    public void Push(T value) {
        dataArray[currentPos++] = value;
    }
    public T Pop() {
        return dataArray[--currentPos];
    }
    public bool Contains(T value) {
        for (int i = 0; i < currentPos; i++) {
            if (value.CompareTo(dataArray[i]) == 0) {
                return true;
            }
        return false;
    }
}

Using Anonymous Types

var myObject = new{ 
    Name = "name",
    Age = "20",
    City="london"
};
Console.WriteLine("Name: {0}", myObject.Name);

Working with Characters

The built-in char type represents a single Unicode character.
The char keyword is an alias to the System.Char struct.

Expressing Characters Using Literals

char myChar = 'a';//Character Literal

Some special characters cannot be expressed directly, and so character literals can be expressed
using an escape sequence or a Unicode encoding.

char myEscapedQuote = '\'';
char myUnicodeQuote = '\u0027';
char myHexQuote = '\x0027';

Character literal escape sequences start with a backslash character () and can be a simple escape
sequence or a Unicode encoding, expressed either in decimal (prefixed with a u) or in hexadecimal
(prefixed with an x).

The only thing to be aware of is when using the addition operator, with statements like these:

char myChar = 'a';
myChar += 'b';//result is 195, not ab

Adding characters like this doesn’t combine them to create ab. Instead, it converts the characters to
their underlying numeric type and adds the numeric value together.

The char keyword is an alias to the System.Char struct, which contains a number of static methods to aid
in working with characters.

char myChar = 'x';
Console.WriteLine("Numeric Value: {0}", Char.GetNumericValue(myChar));//-1
Console.WriteLine("Is Digit? {0}", Char.IsDigit(myChar));//false
Console.WriteLine("Is Letter? {0}", Char.IsLetter(myChar));//true
Console.WriteLine("Is Letter Or Digit? {0}", Char.IsLetterOrDigit(myChar));//true
Console.WriteLine("IsLower? {0}", Char.IsLower(myChar));//true
Console.WriteLine("IsUpper? {0}", Char.IsUpper(myChar));//false
Console.WriteLine("Is Punctuation? {0}", Char.IsPunctuation(myChar));//false
Console.WriteLine("Is Separator? {0}", Char.IsSeparator(myChar));//false
Console.WriteLine("Is Symbol ? {0}", Char.IsSymbol(myChar));//false
Console.WriteLine("Is White space? {0}", Char.IsWhiteSpace(myChar));//false
Console.WriteLine("Convert to Upper: {0}", Char.ToUpper(myChar));//X
Console.WriteLine("Convert to Lower: {0}", Char.ToLower(myChar));//x

Using Strings

A string object represents a read-only sequence of Unicode characters. The string keyword is an alias
for the System.String class, and like all the other built-in type aliases, you can choose to use either the
keyword or the class name or switch between them freely.
When working with strings, it is important to remember that they are reference types and that string field
and variables can be null.
Combining strings using the addition operator (+) or the addition assignment operator (+=) is less efficient
than using the StringBuilder class.
The string class implements an indexer that allows you to access individual characters. You can’t use the indexer to modify the characters since strings are read-only.

// define a new string
string myString = "Hello World";
// use the string indexer and the Length property to enumerate the contents of the string
for (int i = 0; i < myString.Length; i++) {
    Console.WriteLine("Character at position {0}: {1}", i ,myString[i]);
}

The string class implements the IEnumerable interface, which means you can use a foreach loop
to enumerate the characters in a string.

// define a new string
string myString = "Hello World";
// enumerate the contents of the string with a foreach loop
foreach (char c in myString) {
    Console.WriteLine("Character: {0}", c);
}

Using Regular Expressions

You can use regular expressions to search and manipulate strings using the
System.Text.RegularExpressions.Regex class.

Using Class Members

Manipulating Strings
s previously mentioned, string
values are read-only, and so the those methods that seem to edit the value of a string are in fact creating
new string values.

Searching Strings

The string class defines several methods to search strings.
Contains(string)
StartsWith(string)
EndsWith(string)
IndexOf(char)
IndexOf(string)
LastIndexOf(char)
LastIndexOf(string)
IndexOfAny(char[])
LastIndexOfAny(char[])

StringBuilder

The main use of the StringBuilder class is to build strings through multiple append operations.

StringBuilder myBuilder = new StringBuilder();

A StringBuilder object uses a char array to store the characters of the string it represents. This
array will be resized as needed to accommodate the effect of building a string by adding characters.
You can recommend an initial size of array for a StringBuilder object to allocate by providing an int
constructor parameter, like this:

StringBuilder myBuilder = new StringBuilder(100);

If you already have the foundation for the string that you want to build, you can provide this as the
basis of the StringBuilder, like this:

StringBuilder myBuilder = new StringBuilder("Introduction to");

Specifying Alignment

// specify the a string with a format item
string formatString = "The cost of item {0} is {1, 3}";
// use the composite formatting feature
Console.WriteLine(formatString, 1, 100);
Console.WriteLine(formatString, 2, 23);
Console.WriteLine(formatString, 3, 1);
The cost of item 1 is 100
The cost of item 2 is   23
The cost of item 3 is     1

// specify the a string with a format item
string formatString = "The cost of item {0} is {1, -3}";
// use the composite formatting feature
Console.WriteLine(formatString, 1, 100);
Console.WriteLine(formatString, 2, 23);
Console.WriteLine(formatString, 3, 1);
The cost of item 1 is 100
The cost of item 2 is 23
The cost of item 3 is 1

Escaping Braces

If you want to display brace characters in a string that you will use for composite formatting, then you
must use an escape sequence. Two open brace characters ({{) will be interpreted as a single open brace
character ({), and two close brace characters (}}) will be interpreted as a single close brace character (}).

// create a string that has format items and
// escape sequences for brace characters
string formatString = "My name is {{{0}}}";
// use composite formatting
Console.WriteLine(formatString, "Adam");//My name is {Adam}

Using a Format Component

You can mix format components and alignment components in a single format item.

// define a format string with a format string component
string formatString2 = "Cost per item: {0,8:F2}";
// use composite formatting
Console.WriteLine(formatString2, costPerItem);

Creating String Representations of Custom Types

The composite formatting feature relies on creating string representations of objects and values by
calling the ToString method, which is defined in the System.Object class and inherited by all types.
Unfortunately, the default implementation of this method simply returns the name of the type that
contains it
Your custom types can override the ToString method to return string values that are meaningful in
whatever way makes sense, but bear in mind that the ToString method is public, and so you should be
careful if including the values of private fields and properties that you would not want exposed.

Performing Custom Composite Formatting

You can define custom format components and implement support for them using the
System.IFormatProvider and System.ICustomFormatter interfaces.

The first step is to create an implementation of the IFormatProvider interface.

class CarFormatProvider : IFormatProvider {
    public object GetFormat(Type formatType) {
        if (formatType == typeof(System.ICustomFormatter)) {
            return new CarFormatter();
        } else {
            return null;
        }
    }
}

The IFormatProvider interface defines one method, called GetFormat. When this method is called,
the composite formatting system is checking to see whether our custom formatting support is able to
format a specific kind of object. We only want to support custom formatting, so we return null unless
the parameter to the method is the ICustomFormatter type. If the parameter is the ICustomFormatter
type, when we create and return a new instance of the CarFormatter class, which is an implementation of
the ICustomFormatter interface

class CarFormatter : ICustomFormatter {
    public string Format(string format, object arg, IFormatProvider formatProvider) {
        Car myCar = (Car) arg;// cast the object to a car
        StringBuilder myBuilder = new StringBuilder();// create a string builder so we can compose the string
        foreach (char c in format.ToUpper()) {// switch on each character in the format component
            switch (c) {
            case 'M':
                myBuilder.AppendFormat(" Manufacturer: {0}", myCar.Manufacturer);
            break;
            case 'O':
                myBuilder.AppendFormat(" Model: {0}", myCar.Model);
            break;
            case 'C':
                myBuilder.AppendFormat(" Color: {0}", myCar.Color);
            break;
            }
        }
        return myBuilder.ToString().Trim();// return the contents of the StringBuilder as a string
    }
}
// create two Car objects
Car myVolvo = new Car() { Manufacturer = "Volvo", Model = "C30", Color = "Black" };
// create a custom formatter
CarFormatProvider formatProvider = new CarFormatProvider();
// use the custom formatter to format the object
string outputString = string.Format(formatProvider, "Car Details: {0:CMO}", myVolvo);

Using Attributes

不要和Field,Property混淆。

[Flags]
public enum MyProducts{
    Apples = 2,
    Bananas = 4,
    Cherries = 8
}

The name of the attribute (above is Flags) maps to a class derived from System.Attribute.
The convention for attributes is that the name of the attribute class ends with the word Attribute.
The .Net runtime looks for an attribute class by appending Attribute to the name you have specified (above is System.FlagsAttribute class).
if you prefer, you can specify the name fully like:

[FlagsAttribute]
enum MyProducts{

You can also use the fully qualified name of the attribute class(with or without Attribute in the name) like this:

[System.Flags]
enum MyProducts{

[System.FlagsAttribute]
enum MyProducts{

An attribute can have any meaning. When the Flags attribute is applied to an enum,
it means that the string representation an enum value should show which individual values have been combined.

// combine enum values to represent a mix of products
MyProducts productMix = MyProducts.Apples | MyProducts.Bananas;
// print out the product mix
Console.WriteLine("Products: {0}", productMix);//result is"Products: Apples, Bananas"

If remove the attribute from the enum and run the previous code again, the result is: “Products: 6”
You can see that the Flags attribute changes the string representation created by the ToString method.

Applying Attributes with Parameters

Some attributes can be configured with parameters. A good example is System.ObsoleteAttribute,
which you can use to warn other programmers (or yourself) that a type member, such as a method or
property, has been superseded and should not be used.

class Calculator {
    [Obsolete("Don't use this method", true)]
    public int CalculateProduct(int x, int y) {
        return x + y;
    }
}

Above applies the Obsolete(淘汰;廢棄) attribute to the CalculateProduct method. There are two
parameters to this attribute. The first is a message to explain why the method is obsolete, and the second
controls whether using the method constitutes a compiler warning or a compiler error. A value of true
means an error and any code that tries to access the obsolete method won’t compile.
The parameters to the Obsolete attribute can be varied to give different error
messages and to control the behavior of the C# compiler.

Notice that, like the previous attribute, Obsolete has no meaning until it is interpreted by some
other piece of code. In the case of the Flags attribute, the interpretation is found in the ToString method
of the System.Enum class, which is the base for all enums. For the Obsolete attribute, the interpretation is
found in the C# compiler.

Testing for an Attribute

Checking for an attribute is a lot more complex than applying an attribute and relies upon a technique
known as reflection. Attributes can be applied to any type or type member.
Testing for an Attribute Applied to a Class
The simplest test is for an attribute that has been applied to a class (or other type).

[Obsolete("Don't use this class")]
class Calculator {
    public int CalculateProduct(int x, int y) {
        return x + y;
    }
}

Explicitly Running the Garbage Collector

You can run the garbage collector manually by calling the static System.GC.Collect method. I
recommend that you don’t do this in your projects, relying instead on the garbage collector to decide
when it needs to run.

Implementing a Destructor

Some objects need to perform actions before they can be safely destroyed;
the most common examples are where connections to databases and other servers need to be explicitly closed.
The actions you take will depend on your object, but whatever it is you need to do can be included in a destructor.
A destructor is a special method that is called after the garbage collector has identified an object as being unused but before it is deleted from memory.

Destructors vs. Finalizers

If you read the C# and .NET documentation, you will come across the term finalizer, which is used in
much the same way as the term destructor.
The confusion arises because .NET objects can be written in a range of languages (C#, VB.NET, and
so on). All of these objects inherit a method called Finalize, which is called by the garbage collector
immediately prior to an object’s destruction. This is known as finalizing an object. This is a .NET feature
that applies to all .NET languages.
C# implements finalization using destructors. C# objects are not allowed to override the Finalize
method and must implement a destructor instead. The C# compiler interprets a destructor and uses the
code statements it contains to create a Finalize method for your class automatically.

As an example, when the C# compiler sees a destructor like this:

~MyClass() {
    // destructor statements
    Console.WriteLine("Destructor called");
}

it converts it to a Finalize method like this:

protected override void Finalize() {
    try {
        // destructor statements
        Console.WriteLine("Destructor called");
    } finally {
        base.Finalize()
    }
}

You can see from this that your destructor doesn’t need to call the Finalize method and that you
can ignore the directive in the documentation to call the base class implementation of the Finalize
method (because this is done for you when your destructor is compiled).

Problems with Destructors

Destructors must be used with care; there are some important considerations explained in the following sections.

Performance Impact

The first problem with destructors is that they have an impact on the performance of the garbage
collector, which has to stop and perform the code statements your constructor contains. For this reason,
you should not use destructors that are empty (that is, that do not contain code statements) and should
use destructors only when they are essential.

Uncertain Execution

For most programs, the garbage collector is invisible. It decides when it needs to run, and it does so
without your code being aware that collection is taking place. When you are using destructors, this
uncertainty means you don’t know when the statements in your destructor will be performed. Worse, if
your program doesn’t exit properly, the statements in the destructors won’t be performed at all.
There are no guarantees available for destructors. The .NET Framework will make a good faith
attempt to execute them, but you cannot rely on this or predict when it might happen. Taking matters
into your own hands by explicitly invoking a collection will generally make things worse and kill the
performance of your program.

Uncertain Ordering

The garbage collector doesn’t call the destructors in any particular order, so you cannot rely on objects
being destroyed in the order in which they were created. You must write your destructors defensively,
making no assumptions about the state of other objects.

Using Disposal避免Destructor的問題

You can avoid some of the uncertainty of finalization by using a slightly different technique called disposal. To use disposal, your class must implement the System.IDisposable interface.
The System.IDisposable interface defines a single method, called Dispose. When we are done with an object created from the MyClass class, we can call the Dispose method to release any resources that the object has been using.

class MyClass : IDisposable {
    public MyClass() {     
        Console.WriteLine("Constructor called");// constructor statements
    }
    public void DoSomeWork() {
        Console.WriteLine("Doing some work...");
    }
    public void Dispose() {     
        Console.WriteLine("Dispose method called"); // disposal statements
    }
}
MyClass myClass = new MyClass();// create a new MyClass object
myClass.DoSomeWork();// do some work with the object we created
myClass.Dispose();// call the dispose method

Instead of manually calling the Dispose method, you can use the using keyword to define the scope
in which you will use an object. When the .NET runtime reaches the end of the
using statement code block, the Dispose method is called on the object that we provided as a parameter,
in this case, the object created from the MyClass class.

MyClass myClass = new MyClass();// create a new MyClass object
using (myClass) {// do some work with the object we created
    myClass.DoSomeWork();
}

The result is:
Constructor called
Doing some work…
Dispose method called

Using Weak References

The garbage collector will only destroy objects if there are no references to it, meaning that no local
variable or field refers to that object. Sometimes, it can be useful to use references that are effectively
invisible to the garbage collector, which means that the objects they refer to will always be candidates to
be destroyed. Such references are called weak references, while regular references are called strong
references.
A common use of weak references is in a data cache. A cache is a temporary store of data that can be
obtained or generated again but that is stored because generating it takes time (or incurs some other
cost) and that we expect to need again (typically because we expect another user to request the same
data items that we have cached). We want the performance benefits that a cache can bring, but we don’t
want it to tie up all of our system memory, so rather than have to actively manage the data in the cache,
we can use weak references.

class MyClass {
    public MyClass() {
        // constructor statements
        Console.WriteLine("Constructor called");
    }
    ~MyClass() {
        // destructor statement
        Console.WriteLine("Destructor called");
    }
    public void DoSomeWork() {
        Console.WriteLine("Doing some work...");
    }
}
// create a weak reference
WeakReference weakRef = new WeakReference(new MyClass());
// use the weak reference to access the object and so some work
if (weakRef.IsAlive) {
((MyClass)weakRef.Target).DoSomeWork();
}
// run the garbage collector
GC.Collect();
// test to see if the weak reference is still alive
Console.WriteLine("Still alive? {0}", weakRef.IsAlive);

// wait for input before exiting
Console.WriteLine("Press enter to finish");
Console.ReadLine();

The result is:
Constructor called
Doing some work…
Still alive? False
Press enter to finish
Destructor called

Collections, List

The ICollection Interface

All the generic collection classes implement the ICollection interface, which defines the basic
methods that you need to work with a collection irrespective(無關的,不考慮) of the implementation class.
The Members of the ICollection Interface
Add(T) Adds an instance of T to the collection
Clear() Removes all the items from the collection
Contains(T) Returns true if the collection contains a given instance of T
CopyTo(T[], int) Copies the contents of the collection to an array starting at the specified index
Count Returns the number of items in the collection
GetEnumerator() Returns an IEnumerator that yields the collected items
IsReadOnly Returns true if the collection is read-only and false if modifications can be made
Remove(T) Removes an instance of T from the collection

Using the ICollection Interface

// create a new collection
ICollection<string> coll = new List<string>();
// add some items
coll.Add("apple");
coll.Add("orange");
// check to see if a value is in the collection
Console.WriteLine("Contains 'apple': {0}", coll.Contains("apple"));
Console.WriteLine("Contains 'cherry': {0}", coll.Contains("cherry"));
// copy to an array
string[] arr = new string[coll.Count];
coll.CopyTo(arr, 0);
// enumerate the collection contents
foreach (string str in coll) {
    Console.WriteLine("Collection item: {0}", str);
}
// print out the collection count
Console.WriteLine("Count: {0}", coll.Count);
// clear the contents of the collection
coll.Clear();

The ICollection has limited functionality because it contains only those functions that are common to all the generic collections. In effect, it trades(交易,貿易) features in favor of(贊成,支持) flexibility.
If you require more specific functionality, you can work directly with a collection class or, as you’ll see shortly, with the intermediate interface classes that bridge the gap between ICollection and the collections themselves, such as the IList interface

Generic Lists

The IList Interface

IList is derived from ICollection and is one of the intermediate interfaces I mentioned earlier.
It contains additional members that are common to working with lists. There still isn’t access to all the
features of the collection class, but it does manage to maintain a degree of abstraction, which can be a
good balance. The members of the IList interface are summarized as follows

Item An indexer that gets or sets values at a given index. Throws an exception if the index is out of range for the collection.
IndexOf(T) Returns an int for the index of a specific instance of T if it is in the collection or -1 if the instance is not part of the collection.
Insert(int, T) Inserts an instance of T into the collection at a given index, represented by an int.
RemoveAt(int) Removes the element at the specific index from the collection.

Using the IList Interface to Work with Generic Lists

// create a new list
IList<string> list = new List<string>();
// add new items with the Add method
list.Add("apple");
list.Add("orange");
list.Add("cherry");
// use the indexer to set the third value
list[2] = "mango";
// insert an item at the head of the list
list.Insert(0, "banana");
// remove an item
list.RemoveAt(2);
// enumerate the items in the list
foreach (string str in list) {
    Console.WriteLine("List item: {0}", str);
}
// check for items in the list
Console.WriteLine("Index of {0}: {1}", "apple",
list.IndexOf("apple"));
Console.WriteLine("Index of {0}: {1}", "apricot",
list.IndexOf("apricot"));

The List Collection

The class has 3 constructors:
List()
List(IEnumerable)
List(int)
if you have an idea of how many elements will be in the collection, you can improve the
performance of using the collection by using the constructor that lets you specify an initial capacity.
Each time the number of data elements in the collection exceeds the size of the internal array, a larger
array has to be created, and the contents of the collection are copied to it.

In the previous section, we saw the IList interface can be used to work with a list without
knowing what kind of list it is. The disadvantage of using the interface is that you can’t use the
implementation-specific features, and when it comes to the List class, there is so much functionality
available that you may want to consider working with the type directly.

List - Adding, Retrieving, and Removing Items

AddRange(IEnumerable) Appends the contents of an IEnumerable to the collection
GetRange(int, int) Returns a List containing a subset of the items in the collection
InsertRange(int, IEnumerable) Inserts the contents of an IEnumerable into the collection at a specified index
RemoveRange(int, int) Removes a subset of items from the collection

Adding, Getting, and Removing List Items

private static void writeList(string msg, List<string> list) {
    Console.WriteLine(msg);
    foreach (string str in list) {
        Console.WriteLine("List item: {0}", str);
    }
}
// create two lists, specifying the initial elements
List<string> list1 = new List<string>() { "apple", "orange"};
List<string> list2 = new List<string>() { "cherry", "mango"};
// use the add range method to add the
// contents of one list to the other
list1.AddRange(list2);
// enumerate the contents of the list
writeList("Combined List", list1);
// use the GetRange method
List<string> rangelist = list1.GetRange(1, 2);
writeList("GetRange List", rangelist);
// use the InsertRange method
list1.InsertRange(1, rangelist);
writeList("InsertRange List", list1);
// remove a specific item
list1.Remove("orange");
writeList("Removed value orange", list1);
// use RemoveRange
list1.RemoveRange(0, 3);
writeList("RemoveRange List", list1);

Members for Finding List Items, List

Finding Items Using Binary Searches

BinarySearch(T)
BinarySearch(T, IComparer(T))
BinarySearch(int, int, T, IComparer(T))
If the value you are looking for is not in the collection, then the result will be negative. If you NOT
the result with the NOT operator (~), you will get the index of the first element in the list, which is greater
than the item you were looking for. You can use this value to insert the item you searched for and
preserve the ordering of the list.

Finding Items Using Predicates

559

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章