notes on C#
C# language features & gotchas
- C# objects have 1 of 3 types: value, reference, pointer.
- All value types derive from System.ValueType, and all types derive from System.Object.
- Value types are stored on the stack (well sort of), passed by value (copied), and include built-in and immutable structures (i.e., structs and enums). Reference types are stored on the heap, are passed by ref, and include interfaces and mutable structures (i.e., classes)
- managed language (depends on .NET CLR virtual machine)
- "var" can be used to infer a strong type (not same as a dynamic variable that can change type, but as shorthand, AND also must be used when storing an anonymous type object, e.g.,
var foo = new { Bar = "bar"};
)
- support Anonymous Types, Structs and Value Tuples for creating simple property groupings when creating full-blown class is overkill (though anonymouse types are actually implemented as classes behind scenes) and only need to use in local scope (not portable).
- supports SQL-esq declarative query expressions, called LINQ queries (can function like python list compressions)
- support extension methods, syntactic sugar for adding static functionality to an existing type, without having to create a derived type (and without breaking encapsulation similar to the way decorators wrap a function).
- Can turn off compile-time type checking with Dynamics (e.g. to simplify interaction with external objects), i.e., defers type checking to run-time (so can still handle type errors with run-time exceptions ala-python).
- supports namespace aliasing with
using
(this is NOT an import statement. To import in C# you "add a reference" to the build)
- supports simple enum implementation
cards = enum {HEARTS, SPADES, CLUBS}
- supports type safe function objects, called delegates, that can be passed around at runtime (similar to C++ function pointers or callbacks).
- support event listening/handling, which implements something like pub/sub pattern?
see also:
https://www.quora.com/Can-C-do-everything-that-Python-can
classes
- Use static class to group static methods (no properties, i.e. stateless)
- Use abstract class to define base behaivior of related classes. Abstract methods have no imlpementation; they must be implemented by all derived classes.
- Declare class sealed to prevent inheritence or extension (except through extension methods, b/c they do not break encapsulation.)
- Break a class into multiple parts, across mult. files, with partial. Or use add additional behaivor to a partial class without creating a derived class (esp, when partial is created externally, e.g. by a framework)
- 4 levels of accisibility: Public, Private (scoped to enclosing namespace or class), Internal (scoped to project or "assembly"), Protected (only this class and derived classes can access)
- In C#, "properties" define get/set methods for private "variables" and have implicit notation with no "()", get never accepts args and set always accepts one arg which is implicitly passed with keyword "value".
- Also a shorthand notion for properties
public int x { get; set; }
if no need to verify that user is providing valid input to set.
- only single inheritence allowed (interfaces provide similar role as multiple inheritence)
- Derived class can override virtual base class method only.
- acheives polymorphism thru casting (to base types or interface types it implements).
- Base class can also declare methods virtual to ensure overriden implementations gets used even in the case of instance being cast to its base class type polymorphically. Derrived methods that are declared new (explicitely or implicitely by default) on the other hand, will permit casting to base class.
Structs (i.e., Named Tuples) and Value Tuples (i.e., Tuples):
- supports structs, a value type that is basically a simple immutable class for grouping related variables (i.e., namedtuples).
- Prefer structs over very small classes (structs are faster b/c take advantage of high-speed sequential access) with very few instances (becauses class cost of high overhead can't be paid for if not sharing class definition in memory), esp if not passing around, BUT [You can pass a struct by reference using thcaseharp.2000things.com/2011/03/20/276-passing-a-struct-by-reference/))
- as of 7.0, support Value Tuples for grouping related values (i.e., tuples)
- Prefer tuple over struct for concise syntax (including support for single line unpacking)
- If just want to return multiple values, Out parameters are fast and cheap (but require that you pass in a variable, and rely upon mutation).
C# programming guide
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide
- Arrays are an enumerable (i.e., use lazy evaluation, and can be iterated over with a foreach loop) reference type (i.e, is passed by reference)
- You can create an empty array
int[] a = new int[5]
or a full array int[] b = {1,2,3};
, or even an implicitly typed array var c = new[] {1,2,3}
. If empty, numeric elements are set to zero, and reference elements are set to null (e.g., jagged arrays are arrays of arrays)
- C# Arrays are objects (like python and Java, unlike C and C++), with useful methods/properties for sorting, searching, and copying, etc...
- An array can be Single-Dimensional, Multidimensional or Jagged. Use
GetLength(i)
to get length of dimension i in multidimensional arrays.
- Pass array with
out
or ref
to initialize or completely change the reference object that the passed array variable points to. Note, any other variables which referred to the original array would still refer to the original array (kind of a hack to change size of an array, i.e., treat it like a dynamic array).
- Classes are reference types, use to model more complex behavior, or data that is intended to be modified after a class object is created.
- Structs are value types, they are best suited for small data structures that contain data that is not intended to be modified after the struct is created.
...
classes and structs / Local Functions
- Use Object initializer Syntax to create an instance of an object javascript-style, e.g.,
Cat cat = {Name = "Fluffy"}
, without having to invoke a constructor, e.g., Cat cat = new Cat("Fuffy")
- Use Object initializer Syntax with
var
and new
to create an anonymous type that derive directly from object, e.g., var v = new { Amount = 108, Message = "Hello" };
public struct CoOrds
{
public int x, y; // cannot initialize
// no default constructor allowed
public CoOrds(int p1, int p2)
{
x = p1;
y = p2;
}
}
...
// This works (like a class)
CoOrds a = new CoOrds(2,3);
// This also works!
CoOrds b;
b.x = 2;
b.y = 3;
// careful, this makes a full copy!
CoOrds c = b;
- Structs share most of the same syntax as classes, but are more limited.
- fields cannot be initialized unless they are declared as const or static
- struct cannot declare a default constructor (a constructor without parameters) or a finalizer. Constructors with params are allowed.
- Structs are value types, so When a struct is assigned to a new variable, all the data is copied!!!
- Unlike classes, structs can be instantiated without using a new operator.
- A struct cannot inherit from another struct or class, BUT they can implement interfaces via boxing.
- A struct can be used as a nullable type and can be assigned a null value???
// Unnamed Tuple with implicitly named fields: "Item1" and "Item2"
var unnamed = ("apple", 2);
// Named Tuple with explicitly named fields: "a" and "b"
var named = (a: "apple", b: 2);
// Tuple with named fields, "a" and "b", projected, ie, infered.
var a = "apple";
var b = 2;
var projected = (a, b);
// Reassign a tuple with compatible tuple (same size/field types)
named = unnamed;
// Return a tuple from a method like This
return named;
// Or return a tuple literal!
return ("apple", 2);
// Unpack with new explicitly/implicitly typed variables
(string item, int count) = ("apple", 2);
var (item, count) = ("apple", 2);
// Unpack with existing variables
string item;
int count;
(item, count) = ("apple", 2);
ValueTuples
replace existing generic/reference-typed Tuple classes, but prior 4.7, you must add the NuGet package System.ValueTuple
. From here on, tuple refers to ValueTuple.
- Tuples are immutable, fixed-size groupings of heterogeneous objects.
- Tuples enable you to package multiple values in a single object easily.
- Prefer struct or class when defining a type with both data and behavior. Prefer tuple for just data.
- Unnamed tuples, have implicitly named fields named Item1, Item2, etc..
- Named Tuples have explicitly named fields with
name:value
notation.
- Names can infered, i.e., projected, from variable names, if no explicit name is provided.
- Tuples can be a mix of implicitly, projected, and explicitly named field, where projected takes precedence over implicit, and explicit over projected.
- assignment allowed between tuple types that have same number of elements and type or implicit conversions for the types via casting.
- Unpack, ie Deconstruct, a tuple with new or existing variables.
- Delegates are like functions in functional languages or function pointers in C++ (except they are type safe and can be chained together). Because the instantiated delegate is an object, it can be passed around, or swapped out at run-time, i.e. used as a callback function.
- Unlike function objects in javascript or python, C# delegates are type safe, so they must be declared, and all delegate methods must have same signature.
- In some ways delegates are also like interfaces (think strategy pattern) except you can specify multiple delegate instances in a single class. (see When to Use Delegates Instead of Interfaces)
- Specifically, delegates implement the strategy pattern (can also implement strategy pattern with interfaces).
- Delegates can be static functions, instance methods, or anonymous functions.
- lambda expressions are just shorthand anonymous delegates.
- Delegates are intentionally not supported in Java.
- A delegate can call more than one method when invoked. This is referred to as multicasting and is used extensively in event handling.
- The +/- operators can be used to add/remove component delegates to/from a multicast delegate's invocation list to form a chain of delegates.
- Calling a multicast delegate calls the component delegates sequentially in the order that they were added to the "chain".
- Multicast delegate that accepts a
ref
parameter implement a pipeline, i.e., the reference is passed to each delegate in turn, and changes by one are visible to the next.
- Unhandled exceptions will break chain and pass exception to the caller (i.e., no subsequent delegates will be called.)
- Multicast delegates are not intended to be used with delegates that return values b/c only the return value of the last method is returned.
- delegate types are derived from System.Delegate and delegates with more than one method in their invocation list derive from MulticastDelegate.
- Create a Genertic class with type parameters, e.g.,
public class GenericList<T> {...}
, for compile-time type-safty, memory efficiency, and performance (esp. when the list items are value types).
- Consider descriptive names for type parameters if there are more than one, e.g.
TInput, TOutput, TKey, TValue, TSession, etc.
- Prefer generic collections over Boxing/Unboxing, i.e type-casting to/from Object, whenever possible.
- Prefer generic collections over ArrayList, i.e., implicit type-casting to/from Object, for homogenous collections.
- Constrain genertic types with
where
, e.g., public class GenericList<T> where T : BaseTypeClass
, to permit only certain types, e.g., only value or ref types, only those types that inherit from a particular base type, etc.
- Type parameters with no constraint are unbounded
- One or more parameter types may be constrained by one or more constraints. May even be constrained by other parameter types, e.g.
public class SampleClass<T, U, V> where T : V { }
continue on https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/generic-classes
An interface is like an abstract base class but there is no base implementation and and class/struct that implements an interface must implement all its members.
An interface defines a behaivor that a class (OR a struct!) can implement.
C# does NOT support multiple class inheritance, but multiple interfaces are allowed.
Structs cannot inherent from base class, but they can implement interfaces.
Interfaces can only include methods, properties, events, and indexers. Members are implicitly public--no access modifiers are not allowed.
members of the implementing class must be public and non-static, and have the same name and signature as the interface member.
Generic typing is permitted in interface method signatures, eg. interface IEquatable<T> { bool Equals(T obj); }
The interface itself provides no functionality that a class/struct can inherit. However, if a base class implements one or more interface(s), any class that is derived from that base class inherits all its implementation(s)!!! This is how limitation of no multiple inheritance is overcome!!!
Interfaces can also implement other interfaces, and "derived" classes can reimplement the interfaces that is "inherits"
Use Explicit Implementation if a class implements two interfaces that contain a member with the same signature, then create and instance and cast that instance to another instance with as
or ()
casting notation, before calling corresponding method on that instance.
interface IEnglishDims{ float Length(); }
interface IMetricDims{ float Length(); }
class Box : IEnglishDims, IMetricDims
{
float lengthInches;
public Box(float length, float width) { lengthInches = length; }
// 1. Explicit Implementations of each interfaces
float IEnglishDims.Length() { return lengthInches; }
float IMetricDims.Length() { return lengthInches * 2.54f; }
static void Main()
{
// 2. Declare a class instance
Box box1 = new Box(30.0f, 20.0f);
// 3. Cast to desired interface
IEnglishDims eDims = (IEnglishDims)box1;
// 4. Get length in inches
System.Console.WriteLine(eDims.Length());
}
}
(adapted from example)
- The conditional operator
x?y:z
is the only ternary operator in C#.
- Conditionally access members with
x?.y
- Create null default/fallback with
x??y
, called null coalescing.
- Support arrow notation for lambda expressions, e.g.,
(T x) => y