-
Notifications
You must be signed in to change notification settings - Fork 0
Home
The smallest possible C# program would consist of a static Main() function inside of a class. The namespace is actually optional, as is the args parameter.
class Program { static void Main() { } }
Main() Should Not Be Public Code will compile, but is not recommended, according to the MSDN documentation. If Main() is public, other classes could call it. But this violates the intent for Main(), which is meant to only be called once, when the program is invoked.
Using Long Lists of Format Items in Composite Format String string sTest5 = string.Format("blah {0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} ", n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11);
You can do this because the String.Format method actually takes an array of objects as its second parameter: public static string Format(string format, params Object[] args)
Because of the params keyword, you can pass in either an array of objects after the format string, or just a long list of parameters, which will be treated as an array of objects and used in the string substitution
Methods that Support Composite Formatting Methods that take composite format strings include: Console.Write / WriteLine Debug.WriteLine StreamWriter.Write / .WriteLine String.Format StringBuilder.AppendFormat StringWriter.Write / .WriteLine TextWriter.Write / .WriteLine So not only can you do composite formatting with String.Format:
There are several ways to indicate real number (floating point) literals in C#. Real literals are assumed to be of type double, but may also be of type float or decimal, depending on the suffix included with the literal. Suffix / type: f – float d – double m – decimal
Value Types and Reference Types All types in C#, whether built-in or user-defined custom types, can be categorized as either value types or reference types.
Value types Derive from System.ValueType (which derives from System.Object) Allocated on the stack (unless declared inside a reference type) Value type variable contains value directly Assignment makes a copy of the value Passed by value (a copy is made) Not garbage collected–die when they go out of scope Either struct or enum Sealed–can’t inherit from them Reference types Derive from System.Object or another reference type Allocated on the heap Reference type variable contains a reference (pointer) to the object’s contents (or contains null) Assignment creates a new reference to the original object Passed by reference (pointer to object is passed) Garbage collected One of: class, delegate, array or interface Support inheritance
Any class that you create in C# is automatically derived from object. The object type defines the following instance methods: bool Equals(object) void Finalize() int GetHashCode() Type GetType() object MemberwiseClone() string ToString() The object type defines the following static methods: bool Equals(object, object) bool ReferenceEquals(object, object)
When to Use the Decimal Type You should use the decimal type (System.Decimal) for monetary calculations, or whenever you want more digits of precision, to avoid round-off error. For example: 1 2 3 float f1 = 12345678901234567.28f; // 1.23456784E+16 double d1 = 12345678901234567.28d; // 12345678901234568.0 decimal dc1 = 12345678901234567.28m; // 12345678901234567.28 The decimal type gives you greater precision, but doesn’t support storing numbers as large as float or double. This is because it stores all digits, rather than storing the mantissa and exponent of the number. The decimal type also requires more storage space. float – 4 bytes (±1.5e−45 to ±3.4e38, 7 digit precision) double – 8 bytes (±5.0e−324 to ±1.7e308, 15-16 digit precision) decimal – 16 bytes (±1.0 × 10−28 to ±7.9 × 1028, 28-29 digit precision)
Generate Exceptions on Integer Overflow Using Checked Operator By default, when an overflow occurs as a result of an arithmetic operation on an integer type, the result wraps. 1 2 int n1 = int.MaxValue; // 2147483647 (0x7FFFFFFF) n1 = n1 + 1; // Now -2147483648 (wrapped) You can instead cause an exception to be thrown whenever an integer overflow condition occurs. To do this, use the checked keyword on the expression: 1 2 int n1 = int.MaxValue; // 2147483647 (0x7FFFFFFF) n1 = checked(n1 + 1); // Throws OverflowException
Sorting One-Dimensional Arrays
You can sort a one-dimensional array of elements using the static Array.Sort method. This requires that the underlying type of the array elements implements the IComparable interface. Types that implement IComparable include: all built-in numeric types (e.g. int, float, double), char, string and DateTime types.
An Example of Implementing ICloneable for Deep Copies To do a deep copy of the Person class, we need to copy its members that are value types and then create a new instance of Address by calling its Clone method. public class Person : ICloneable { public string LastName { get; set; } public string FirstName { get; set; } public Address PersonAddress { get; set; }
public object Clone()
{
Person newPerson = (Person)this.MemberwiseClone();
newPerson.PersonAddress = (Address)this.PersonAddress.Clone();
return newPerson;
}
} The Address class uses MemberwiseClone to make a copy of itself public class Address : ICloneable { public int HouseNumber { get; set; } public string StreetName { get; set; }
public object Clone()
{
return this.MemberwiseClone();
}
} Person herClone = (Person)emilyBronte.Clone(); Using Serialization to Implement Deep Copying This method will work only if the class to be copied, as well as all classes referenced by it (directly or indirectly), are serializable. For example, if we have a Person class that has some value-typed members, but also a reference to an instance of the Address class, both the Person and Address classes have to be serializable in order to use this technique. Here’s a generic method that makes a deep copy using serialization, serializing the object into a stream and then deserializing into a new object.
public static T DeepCopy(T obj) { using (MemoryStream stream = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, obj); stream.Position = 0;
return (T)formatter.Deserialize(stream);
}
} using this method to deep copy a Person: Person bronteClone = DeepCopy(emily);
Using IEnumerable.Aggregate to Perform a Calculation Based on All Elements in an Array Aggregate works by specifying a function that will get called once for each element in the array (except for the first element). The function’s arguments are: On 1st pass: 1st and 2nd elements are passed to the function On 2nd thru (n-1)th pass: result of previous pass is passed as 1st parameter, next element as 2nd parameter The return value is used to store the result of your aggregate function and is passed back to the function on the next pass. Here’s an example:
public static Person CombinePeople(Person prevResult, Person next) { return new Person(prevResult.FirstName + next.FirstName, prevResult.LastName + next.LastName, prevResult.Age + next.Age); }
// Example of calling Aggregate Person pCombined = folks.Aggregate((Func<Person,Person,Person>)CombinePeople);
The System.Linq namespace includes several other extension methods for IEnumerable that can be used to aggregate values: Max – Get the maximum value Min – Get the minimum value Sum – Get the sum of all values
int[] scores = { 89, 98, 72, 100, 83 }; int sum = scores.Sum(); // 442 int min = scores.Min(); // 72 int max = scores.Max(); // 100 double avg = scores.Average(); // 88.4
Any Clause of the for Loop May Be Left Empty // Loop until i reaches 10, no matter what its starting value for (; i <= 10; i++) { Console.WriteLine(i); }
// Loop forever, incrementing i for (int i = 0; ; i++) { Console.WriteLine(i); }
// Get 'er done, Hercules for (int laborNum = 1; laborNum <= 12; ) { bool success = HerculesLabors(laborNum); if (success == true) laborNum++; }
// Run forever for (;;) { AskUserATriviaQuestion(); TellThemTheCorrectAnswer(); }
Value Types on the Heap Value types are normally allocated on the stack. They can, however, be allocated on the heap–when they are declared within an object. When an object is instantiated, memory for the entire object, including all of its data, is allocated on the heap. For example, assume we define a Person class with some properties that are value types (int) and some that are reference types (string).
When you create an instance of the Person object, Person p = new Person("Zsa Zsa", "Gabor"); the Person object is created on the heap and all of its data members, including the Age and HeightInInches value types, are also on the heap
Static Data and Constants Are Stored on the Heap
Static data and constants defined in a C# program are stored on the heap. Since they exist for the lifetime of the application, they do not need to be garbage collected and are therefore stored in a loader heap, rather than the normal Garbage Collected heap. Specifically, static data is stored on the high-frequency heap–one of the loader heaps that exists for each AppDomain.
Nullable Types normal range of values or represent a null value. Any value type can be used as a nullable type by adding a trailing ? to the type name.
int i = 12; // regular int, can't be null
int? j = 22; // Nullable int, can be null j = null; // Can also be null
The ? character that indicates a nullable type is really a shortcut to the System.Nullable structure, where T is the type being made nullable. In other words, using int? is equivalent to using System.Nullable<System.Int32>. You can make any value type nullable using the Nullable syntax. You’d typically do this for your own custom struct or enum types.
The Nullable type lets us make any value type nullable. But why is this useful? When might we want to store a null value in a type, in addition to the normal range of values? It’s often useful to represent the fact that a variable doesn’t have a value. For example, assume that we have a class that stores some information about a book that we have read. We might have a DateStarted and a DateFinished field:
DateTime DateStarted;
DateTime DateFinished; If we want to be able to store information representing a book that has been started, but not yet finished, we’d want to store a value for DateStarted, but no value for DateFinished. With a normal DateTime value, we couldn’t do this. Instead, we make both fields nullable, allowing us to store a null value for either. Both the built-in nullable types (e.g. int?) and any Nullable types that you use have a HasValue property that lets you determine whether a particular nullable object has a value or not. HasValue is true if the object has a value of the corresponding type HasValue is false if the object has a null value For example, if you are using a nullable int: int? herAge = null; And you later want to see if this variable has a value (of type int), you can do this:
if (herAge.HasValue)
Console.WriteLine("She is {0} yrs old", herAge);
HasValue is useful in cases when you want to perform an operation on an object, where the operation is only valid if the object is non-null.
Using the Null-Coalescing (??) Operator e ?? operator, know as the null-coalescing operator, is often used with nullable types. The ?? operator is used in an expression of the form: 1 operand ?? nullval This expression evaluates to: operand, if operand is non-null nullval, if operand is null
int? age = null; // Nullable age -- might not have a value
// Later: assign to a non-nullable uint. // Store the age (if non-null) // Store 0 (if null) uint ageStore = age ?? 0;
Final Operand When Using Null-Coalescing (??) Operator You can use several ?? (null-coalescing) operators in a single expression. For example: int result = i ?? j ?? k ?? 0; // i, j and k are all of type int? Notice that the final operand in the long expression is an integer constant. If we tried to use an int? variable as the final operand, we’d get a compile-time error indicating that we can’t implicitly convert the int? to an int. (Because the int? variable might be null, which can’t be assigned to an int). int result = i ?? j ?? k; // Compile-time error Whenever you use this form of an expression with the ?? operator, assigning a value to a non-nullable type, the type of the 2nd operand must match the type of the variable being assigned to. (Or be implicitly converted to the target type).
The ?? operator is often used with nullable value types, assigning a value to an equivalent non-nullable type (e.g. int? to int). But it can also be used with reference types. The behavior with reference types is the same as with value types–the value of the expression is equal to the first operand, if non-null, or the second operand if the first operand is null. The Nullable type has a GetValueOrDefault(T) method that behaves in a way identical to the ?? operator. It returns the value of the object, if non-null, or the value of the parameter passed in if the object is null.
Nullable<Mood> myMood = null;
Mood mood2 = myMood ?? Mood.Happy; // result = Happy, since myMood is null Mood mood3 = myMood.GetValueOrDefault(Mood.Happy); // same as above
When Unboxing, You Must Match the Original Type Exactly
We know that we can do explicit casts when assigning from one type to another and the assignment will work if the value being assigned can be represented by the target type.
int i = 1964;
uint ui = (uint)i; // cast works fine But when casting as part of an unboxing operation, the target type must match the original type exactly.
int i = 1964;
object o = i; // box
// Attempted unbox to different type // Throws InvalidCastException uint ui = (uint)o; This cast, as part of the unboxing operation, is allowed at compile time, but throws an InvalidCastException at run-time.
Encapsulation – Hide implementation details in a class from users of the class, exposing only a public interface Inheritance – Derive a subclass from a parent class, inheriting data and behavior from the parent, in an “is-a” relationship. Inheritance defines a hierarchy of classes. All classes ultimately inherit from System.Object. Polymorphism – Any subtype may be used where a parent type (or type higher up in the class hierarchy) is expected. Conversely, a variable of a particular class will be treated as the appropriate subclass.
How Properties Look Under-the-Covers The typical pattern for implementing a property in a C# class is shown below–you define a private backing variable in which to store the property value, as well as get and set accessors that read/write the property value.
By Default, Parameters Are Passed by Value By default, parameters are passed to a method by value, which means that a copy of each parameter is made and passed to the method. Because the method works with a copy of the data, changes made to a parameter are not visible outside the method. Parameters passed by value can be thought of as input parameters. For example, assume that we have the following Bark method, which takes a parameter named barkPhrase. Notice that we attempt to change the value of barkPhrase before returning to the caller.
public void Bark(string barkPhrase)
{ Console.WriteLine("{0} says: {1}", Name, barkPhrase);
barkPhrase = "Yowza!"; // Just changing our copy
} Assume that we called the Bark method as follows:
string myBark = "Wooferooni";
kirby.Bark(myBark); We can verify in the debugger that the myBark variable does not change when we call the Bark method. Passing Reference Types by Value Recall that by default parameters are passed by value to a method. A copy of the variable is passed to the method, which means that if the method changes the parameter’s value, the change won’t be seen by the calling code. If a parameter’s type is a reference type, however, what is passed by value to the method is a reference to the object. This means that while the method can’t change the reference, it can change aspects of the object that is referenced. As an example, assume that we have a Dog.BarkAt method that takes a reference to another Dog. In the code below, the BarkAt method actually changes the target dog’s BarkPhrase.
public void BarkAt(Dog target)
{ Console.WriteLine("I say {0} to {1}", BarkPhrase, target.Name);
// I can change target dog's properties
target.BarkPhrase = "I-am-a-dope";
} This change is seen by the calling code.
You Can’t Prevent a Method from Changing the Contents of Reference Types We saw that even when you pass a variable by value to a method, if that variable is a reference to an object, the method can change the contents of the object. C# doesn’t have a built-in language construct that allows you to tell the compiler to prohibit a method from changing the contents of an object that a parameter references. (In C++, we can do this with the const keyword). In C#, if you want to prevent a method from changing an object, you need to first clone your original object and pass a copy of the object to the method. (You’ll want to make a deep copy of the object). Here’s an example of using the DeepCopy method to pass a copy of the object. kirby.BarkAt(DeepCopy(jack));
You can use out parameters as a way of returning multiple data values from a method to its caller. Because the intention of an output parameter is to return data to the caller, you must assign some value to every parameter marked as out before you can return from a method. The compiler will enforce this rule Passing a Reference Type by Reference When you pass a reference type by value to a method, you pass in a reference to the object. The method can change the object, but can’t change the reference.
public static void AnnotateMotto(Dog d)
{ // Change the dog's motto d.Motto = d.Motto + " while panting"; } You can also pass a reference type by reference, which means that the method can change not only the contents of the object pointed to, but change the reference, causing it to refer to a different object.
public static void FindNewFavorite(ref Dog fav)
{ fav.Motto = "No longer the favorite";
foreach (Dog d in AllDogs)
{
if (d.Motto.Contains("ball"))
fav = d;
}
} Differences Between ref and out Parameters Ref and out parameters in C# are implemented in the same way, in the compiled code. In both cases, a parameter is passed by reference, giving the method called a chance to change the underlying value. ref and out parameters different purposes: ref parameters are for input/output – a value is passed into a method and a new value is passed out out parameters are for output – a value is passed out of a method The compiler enforces the following rules: ref parameters A value must be assigned to parameter before method is called Parameter may be referenced inside method before being written to Parameter may be written to before returning from method out parameters A value may be assigned before method is called (but cannot be used inside method) Parameter may not be referenced inside method before being written to Parameter must be written to before returning from method Parameter Modifier Summary No modifier – value types Copy of value passed to method Method can read value No modifier – reference types Reference to object passed to method Method can read/write object contents ref modifier – value types Reference to variable passed to method Method can read/write variable ref modifier – reference types Reference to object reference passed to method Method can read/write object contents Method can change reference to point to new object out modifier – value types Reference to variable passed to method Method must write new value to variable out modifier – reference types Reference to object reference passed to method Method must change reference to point to new object Can’t Overload if Methods Differ Only by ref and out Modifiers We cannot overload a method if the parameters differ only in a ref vs out modifiers:
} public void FindDog(ref Dog aDog)
{ // implementation here }
// Compiler will disallow this overload public void FindDog(out Dog aDog) { // implem
A Static Constructor Initializes Static Data In the same way that you can define instance constructors to initialize instance data in a class, you can define a static constructor to initialize any static data in the class. A static constructor is defined using the static keyword and the name of the class. The constructor takes no arguments and you can only define one static constructor. You do not declare a static constructor as either public or private.
public static string Motto { get; set; }
public static int NumDogs { get; private set; }
static Dog() { Motto = "We're like wolves. Only friendlier."; NumDogs = 0; } You cannot dictate when a static constructor will be called and you can’t call it directly. The compiler guarantees that it will be called automatically before you create any instances of your class or access any static data.
You cannot dictate when a static constructor will be called and you can’t call it directly. More specifically, a static constructor will be called just before you do one of the following: Create an instance of the type Access any static data in the type Start executing Main method (in the same class as the static constructor)
Accessibility of Fields in a Class You can apply access modifiers to fields defined in a class to define their accessibility. Accessibility dictates what other code is allowed to read and the write the value of a field. public – All code can read/write the field private – Only code in the defining class can read/write the field protected – Code in the defining class or derived classes can read/write the field internal – All code in the defining assembly can read/write the field protected internal – Code in the defining assembly or in derived classes can read/write the field
public class Dog { // All code can access public string Nickname;
// Only code in this class can access
private string genericDogSecretName;
// Code in this class or subclass can access
protected int totalBarkCount;
// Code in same assembly can access
internal int invokeCount;
// Code in same assembly or derived classes can access
protected internal int barkInvokeCount;
} Accessibility of Properties in a Class public – All code can read/write the property private – Only code in the defining class can read/write the property protected – Code in the defining class or derived classes can read/write the property internal – All code in the defining assembly can read/write the property protected internal – Code in the defining assembly or in derived classes can read/write the property
Accessibility of Methods in a Class public – Any code can call the method private – Only code in the defining class can call the method protected – Code in the defining class or derived classes can call the method internal – Any code in the defining assembly can call the method protected internal – Code in the defining assembly or in derived classes can call the method
Accessibility of Constructors in a Class public – All code has access private – Only code in defining class has access protected – Code in defining class or derived classes has access internal – All code in defining assembly has access protected internal – Code in defining assembly or in derived classes has access Access Modifiers Are Not Allowed on Static Constructors Because you can’t call a static constructor directly, you can’t include an access modifier (e.g. public, private) when defining a static constructor. Static constructors are defined without access modifiers. Declaring and Using Constants
You Can’t Use the static Modifier On a Constant
Although constants can be treated as static member variables, you cannot include the static keyword when declaring a constant. A constant is always effectively static, so the static keyword would be redundant. The compiler will generate an error if you include it. // This is ok const string Demeanor = "friendly";
// !! Compiler error - constant cannot be marked static static const int TheAnswer = 42;
Accessibility of Constants public – All code has access private – Only code in the defining class has access protected – Code in the defining class or derived classes has access internal – All code in the defining assembly has access protected internal – Code in the defining assembly or in derived classes has access
Generic Type vs. Constructed Type Once a generic type is provided with type arguments, it is known as a constructed type. Here is the definition of a generic type:
// A generic type public class ThingContainer<TThing1, TThing2> { public TThing1 Thing1; public TThing2 Thing2; }
Assigning a struct to a New Variable Makes a Copy Copying a struct Will Not Copy Underlying Reference Types Assigning a struct to a new variable will make a copy of the contents of the struct. After the assignment, two copies of the struct will exist, which can be changed independently. If the struct contains any reference-typed fields, only a reference to an object is copied. Both copies of the struct will then refer to the same object. Assume that we define the following struct:
public struct MovieInfo { public string Title; public int Year; public Person Director; }
If we make a copy of the MovieInfo struct through an assignment, the Director field in the two copies will point to the same Person object.
MovieInfo goneWithWind; goneWithWind.Title = "Gone With the Wind"; goneWithWind.Year = 1939; goneWithWind.Director = new Person("Victor", "Fleming");
// Actually makes a copy of entire struct MovieInfo goodClarkGableFlick = goneWithWind;
// Change director's name goneWithWind.Director.FirstName = "Victoria";
Console.WriteLine(goneWithWind.Director); // Victoria Fleming Console.WriteLine(goodClarkGableFlick.Director); // Victoria Fleming
Derived Classes Do Not Inherit Constructors A derived class inherits all of the members of a base class except for its constructors. You must define a constructor in a derived class unless the base class has defined a default (parameterless) constructor. If you don’t define a constructor in the derived class, the default constructor in the base class is called implicitly. When you define a constructor in a derived class, you can call a constructor in the base class by using the base keywor
Calling a Base Class Constructor Implicitly vs. Explicitly In a derived class, you can call a constructor in the base class explicitly using the base keyword. public class Terrier : Dog { public string Attitude { get; set; }
public Terrier(string name, int age, string attitude)
: base(name, age)
{
Attitude = attitude;
}
} If you don’t explicitly call a base class constructor, the default (parameterless) constructor is called implicitly. public Terrier(string name, int age, string attitude) { // Default Dog constructor has already been called // at this point. Name = name; Age = age; Attitude = attitude; }
If you do omit the base keyword, the base class must define a default (parameterless) constructor. If it doesn’t, the compiler will complain that the base class doesn’t have a constructor that takes 0 arguments.
Advertisements
Use the new Keyword to Replace a Method in a Base Class There are times when you might want to replace a method in a base class with a new method in the derived class, having the same name. You can do this using the new keyword. Assume a Dog class has a Bark method
public class Dog { public void Bark() { Console.WriteLine("Generic dog {0} says Woof", Name); } }
You can provide a new version of this method in a class that derives from Dog, using the new keyword. This new method hides the method in the base class. public class Terrier : Dog { public new void Bark() { Console.WriteLine("Terrier {0} is barking", Name); }
Dog objects will now invoke Dog.Bark and Terrier objects will invoke Terrier.BarkWe could also use a variable of type Dog (the base class) to refer to an instance of a Terrier (the derived class). If we then call the Bark method using this base class variable, the Bark method in the base class is called, even though we’re invoking the method on an instance of the derived class. Virtual Methods Support Polymorphism In C#, polymorphism is implemented using virtual methods. A virtual method has an implementation in the base class that can be overridden in a derived class. When the method is invoked on an object of the base class’ type, the specific method to be called is determined at run-time based on the type of the underlying object. A virtual method is defined in the base class using the virtual keyword.
In C#, virtual methods support polymorphism, by using a combination of the virtual and override keywords. With the virtual keyword on the base class method and the override keyword on the method in the derived class, both methods are said to be virtual. Methods that don’t have either the virtual or override keywords, or that have the new keyword, are said to be non-virtual. When a virtual method is invoked on an object, the run-time type of the object is used to determine which implementation of the method to use. When a non-virtual method is invoked on an object, the compile-time type of the object is used to determine which implementation of the method to use.