C# – IEnumerable and IEnumerator

IEnumerable and IEnumerator

Related Article

IEnumerator Interface

Elements in a collection are enumerated in a forward manner only.

public interface IEnumerator
{
	bool MoveNext();
	object Current { get; }
	void Reset();
}

MoveNext – advances the current element to the next position or returns false if there are no more elements in the collection.

Current – returns the element at the current position.

Reset – moves back to the start of the collection.

Collections do not implement enumerators, instead they provide enumerators via the interface IEnumerable.

public interface IEnumerable
{
	IEnumerable GetEnumerator();
}

IEnumerable

Provides flexibility in that the iteration logic can be farmed off to another class.

Several consumers can enumerate the collection at once without interfering with each other.

IEnumerable can be thought of as IEnumeratorProvider.

string s = "Hello";

// Because string implements IEnumerable, we can call GetEnumerator():
IEnumerator rator = s.GetEnumerator();

while (rator.MoveNext())
{
	char c = (char) rator.Current;
	Console.Write (c + ".");
}

Console.WriteLine();

// Equivalent to:

foreach (char c in s)
	Console.Write (c + ".");

IEnumerable<T> and IEnumerator<T>

IEnumerator and IEnumerable are nearly always implemented in conjunction with their extended generic versions.

public interface IEnumerator : IEnumerator, IDisposable
{
	T Current { get; }
}

public interface IEnumerable : IEnumerable
{
	IEnumerator GetEnumerator();
}

By defining typed version of Current and GetEnumerator, these interfaces strengthen static type safety and avoid the boxing overhead.

Arrays automatically implement IEnumerable<T>.

IEnumerable<T> and IDisposable

IEnumerator<T> inherits from IDisposable.

This allows enumerators to hold references to resources such as database connections to ensure that those resources are released when enumeration is complete.

foreach (var element in somethingEnumerable) { ... }

translates into:

using ( var rator = somethingEnumerable.GetEnumerator())
	while (rator.MoveNext())
	{
		var element = rator.Current;
		...
	}

The using block ensures disposal.

IEnumerable s = "Hello";

using (var rator = s.GetEnumerator())
	while (rator.MoveNext())
	{
		char c = (char) rator.Current;
		Console.Write (c + ".");
	}

Non Generic Interface

void Main()
{
   Count("the quick brown fox".Split());
}

public static int Count(IEnumerable e)
{
   int count = 0;
   foreach (object element in e)
   {
      var subCollection = element as IEnumerable;
      if (subCollection != null)
          count += Count (subCollection);
      else
          count++;
   }
   return count;
}

IEnumerable or IEnumerable<T> can be implemented for one of the reasons:

  • To support the foreach statement.
  • To inter-operate with anything expecting a standard collections.
  • To meet the requirements of a more sophisticated collection interface.
  • To support collection initializers.

To implement IEnumerable/IEnumerable<T>:

  • If class is wrapping another collection, return the wrapped collection’s enumerator
  • Using the iterator yield return
  • By instantiating your own implementation

Using Iterator

void Main()
{
	foreach (int element in new MyCollection())
		Console.WriteLine (element);
}

public class MyCollection : IEnumerable
{
	int[] data = { 1, 2, 3 };
	
	public IEnumerator GetEnumerator()
	{
		foreach (int i in data)
			yield return i;
	}
}

Using Iterator using Generic

void Main()
{
	foreach (int element in new MyGenCollection())
		Console.WriteLine (element);
}

public class MyGenCollection : IEnumerable<int>
{
	int[] data = { 1, 2, 3 };

	public IEnumerator GetEnumerator()
	{
		foreach (int i in data)
			yield return i;
	}

	IEnumerator IEnumerable.GetEnumerator()     // Explicit implementation
	{                                           // keeps it hidden.
		return GetEnumerator();
	}
}

Using Iterator Method

The yield statement allows for an easier variation. Instead of writing a class the iteration logic can be moved into a method returning a generic IEnumerable<T> and let the compiler take care of the rest.

void Main()
{
	foreach (int i in Test.GetSomeIntegers())
		Console.WriteLine (i);
}

public class Test
{
	public static IEnumerable  GetSomeIntegers()
	{
		yield return 1;
		yield return 2;
		yield return 3;
	}
}

C# – Order Comparison

Order Comparison

Protocols are:

1. The IComparable Interfaces

2. The < and > Operators

IComparable Interfaces

Used for general-purpose sorting algorithms.

System.String implements IComparable interfaces.

string[] colors = { "Green", "Red", "Blue" };
Array.Sort (colors);
foreach (string c in colors) Console.Write (c + " ");   // Blue Green Red

// The IComparable interfaces are defined as follows:
//   public interface IComparable       { int CompareTo (object other); }
//   public interface IComparable { int CompareTo (T other);      }

Console.WriteLine ("Beck".CompareTo ("Anne"));       // 1
Console.WriteLine ("Beck".CompareTo ("Beck"));       // 0
Console.WriteLine ("Beck".CompareTo ("Chris"));      // -1

If a comes after b, a.CompareTo(b) returns a positive number.

If a is the same as b, a.CompareTo(b) returns 0.

If a comes before b, a.CompareTo(b) returns a negative number.

< and > Operators

The < and > operators are more specialized and mostly used for numeric types. They are efficient for bytecode translation and used for intensive algorithms.

// Some types define < and > operators:
bool after2010 = DateTime.Now > new DateTime (2010, 1, 1);

// The string type doesn't overload these operators (for good reason):
bool error = "Beck" > "Anne";       // Compile-time error
public struct Note : IComparable, IEquatable, IComparable
{
	int _semitonesFromA;
	public int SemitonesFromA => _semitonesFromA;
	
	public Note (int semitonesFromA)
	{
		_semitonesFromA = semitonesFromA;
	}
	
	public int CompareTo (Note other)            // Generic IComparable
	{
		if (Equals (other)) return 0;    // Fail-safe check
		return _semitonesFromA.CompareTo (other._semitonesFromA);
	}
	
	int IComparable.CompareTo (object other)     // Nongeneric IComparable
	{
		if (!(other is Note))
			throw new InvalidOperationException ("CompareTo: Not a note");
		return CompareTo ((Note) other);
	}
	
	public static bool operator < (Note n1, Note n2)
		=> n1.CompareTo (n2) < 0;
	
	public static bool operator > (Note n1, Note n2)
		=> n1.CompareTo (n2) > 0;
	
	public bool Equals (Note other)    // for IEquatable
		=> _semitonesFromA == other._semitonesFromA;
	
	public override bool Equals (object other)
	{
		if (!(other is Note)) return false;
		return Equals ((Note) other);
	}
	
	public override int GetHashCode()
		=> _semitonesFromA.GetHashCode();
	
	public static bool operator == (Note n1, Note n2)
		=> n1.Equals (n2);
	
	public static bool operator != (Note n1, Note n2)
		=> !(n1 == n2);
}

static void Main()
{
	Note n1 = new Note (1);	
	Note n2 = new Note (2);
	(n2 > n1).Dump();
}

C# – Equality Comparison

Equality Comparison

Value Versus Referential Equality (== or !=)

Value Equality – two values are equivalent in some sense.

Referential Equality – two references refer to exactly the same object.

By default the value types use value equality, reference types use referential equality.

Value Equality

// Simple value equality:
int x = 5, y = 5;
Console.WriteLine (x == y);   // True (by virtue of value equality)

DateTimeOffset

The two DateTimeOffsets struct refer to the same point in time.

// A more elaborate demonstration of value equality:
var dt1 = new DateTimeOffset (2010, 1, 1, 1, 1, 1, TimeSpan.FromHours(8));
var dt2 = new DateTimeOffset (2010, 1, 1, 2, 1, 1, TimeSpan.FromHours(9));
Console.WriteLine (dt1 == dt2);   // True (same point in time)

Reference Equality

// Referential equality:
Foo f1 = new Foo { X = 5 };
Foo f2 = new Foo { X = 5 };
Console.WriteLine (f1 == f2);   // False (different objects)

//Are equal because they refer to the same object
Foo f3 = f1;
Console.WriteLine (f1 == f3);   // True (same objects)

Equality Uri Class

// Customizing classes to exhibit value equality:
Uri uri1 = new Uri ("http://www.linqpad.net");
Uri uri2 = new Uri ("http://www.linqpad.net");
Console.WriteLine (uri1 == uri2);              // True

Standard Equality Protocols

1. The == and != operators

2. The virtual Equals method in object

3. The IEquatable<T> interface

== and !=

int x = 5;
int y = 5;
Console.WriteLine (x == y);      // True

//Object is reference type thus uses referential equality to compare x and y.
//Returns false because they refer to two different boxed objects on the heap.
object x = 5;
object y = 5;
Console.WriteLine (x == y);      // False

The virtual Object.Equals method

Object.Equals method is defined in System.Object and available to all types and resolved at runtime.

void Main()
{
	object x = 5;
	object y = 5;
	Console.WriteLine (x.Equals (y));      // True
	
	Console.WriteLine (AreEqual (x, y));		// True
	Console.WriteLine (AreEqual (null, null));	// True
}

// Here's an example of how we can leverage the virtual Equals method:
public static bool AreEqual (object obj1, object obj2)
{
	if (obj1 == null) return obj2 == null;
	return obj1.Equals (obj2);
	// What we've written is in fact equivalent to the static object.Equals method!
}

The static object.Equals method

The static helper method does the work of AreEqual accepting two arguments.

static void Main()
{
	object x = 3, y = 3;
	Console.WriteLine (object.Equals (x, y));   // True
	x = null;
	Console.WriteLine (object.Equals (x, y));   // False
	y = null;
	Console.WriteLine (object.Equals (x, y));   // True
}

// Here's how we can use object.Equals:
class Test 
{
	T _value;
	public void SetValue (T newValue)
	{
		if (!object.Equals (newValue, _value))
		{
			_value = newValue;
			OnValueChanged();
		}
	}

	protected virtual void OnValueChanged() { /*...*/ }
}

The static object.ReferenceEquals method

class Widget
{
	// Let's suppose Widget overrides its Equals method and overloads its == operator such
	// that w1.Equals (w2) would return true if w1 and w2 were different objects.
	/*...*/
}

static void Main()
{
	Widget w1 = new Widget();
	Widget w2 = new Widget();
	Console.WriteLine (object.ReferenceEquals (w1, w2));     // False
}

The IEquatable interface

IEquatable<T> implementation gives the same result as calling object’s virtual Equals method.

class Test where T : IEquatable
{
	public bool IsEqual (T a, T b) =>  a.Equals (b);     // No boxing with generic T
}

static void Main()
{
	new Test().IsEqual (3, 3).Dump();
}

Removing the generic constraint, the class will still compile but will bind to the slower object.Equals.

When Equals and == are not equal

The double type’s == operator ensures that double.NaN cannot equal to anything else. It will always result as false. But Equals method will always apply reflexive equality.

Collections and dictionaries always rely on Equals behaving this way.

// With value types, it's quite rare:
double x = double.NaN;
Console.WriteLine (x == x);            // False
Console.WriteLine (x.Equals (x));      // True

// With reference types, it's more common:
var sb1 = new StringBuilder ("foo");
var sb2 = new StringBuilder ("foo");
Console.WriteLine (sb1 == sb2);          // False (referential equality)
Console.WriteLine (sb1.Equals (sb2));    // True  (value equality)

Equality and Custom Types

Value types use value equality.

Reference types use referential equality.

Struct’s Equals method applies structural value equality by default.

When to override the equality method

When there is a need to change the meaning of equality.

When there is need to speed up equality comparisons for structs.

public struct Area : IEquatable 
{
	public readonly int Measure1;
	public readonly int Measure2;
	
	public Area (int m1, int m2)
	{
		Measure1 = Math.Min (m1, m2);
		Measure2 = Math.Max (m1, m2);
	}
	
	public override bool Equals (object other)
	{
		if (!(other is Area)) return false;
		return Equals ((Area) other);        // Calls method below
	}
	
	public bool Equals (Area other)        // Implements IEquatable
		=> Measure1 == other.Measure1 && Measure2 == other.Measure2;
	
	public override int GetHashCode()
		=> Measure2 * 31 + Measure1;    // 31 = some prime number
	
	public static bool operator == (Area a1, Area a2)
		=> a1.Equals (a2);
	
	public static bool operator != (Area a1, Area a2)
		=> !a1.Equals (a2);
}

static void Main()
{
	Area a1 = new Area (5, 10);
	Area a2 = new Area (10, 5);
	Console.WriteLine (a1.Equals (a2));    // True
	Console.WriteLine (a1 == a2);          // True
}

C# – Tuples and Guid Struct

Tuples

Tuples are set of generic classes for holding a set of differently typed elements. Where each has read-only properties called Item1, Item2.

Creating Tuples

1. Using Constructor

var t = new Tuple (123, "Hello");

2. Static Helper method

Tuple t = Tuple.Create(123, "Hello");

3. Using Implicit Type

var t = Tuple.Create(123, "Hello");

Accessing the Tuple Properties

Console.WriteLine (t1.Item1 * 2);         // 246
Console.WriteLine (t1.Item2.ToUpper());   // HELLO

// The alternative sacrafices static type safety and causes boxing with value types:

object[] items = { 123, "Hello" };
Console.WriteLine ( ((int)    items[0]) * 2       );   // 246
Console.WriteLine ( ((string) items[1]).ToUpper() );   // HELLO

Tuples are convenient in returning more than one value from a method or creating collections of value pairs. Alternative to tuples is to use an object array but with limitations such as boxing and unboxing.

Comparing Tuples

Tuples are classes so reference types. The equality operator returns false where as the Equals method is overridden to compare each individual element.

var t1 = Tuple.Create (123, "Hello");
var t2 = Tuple.Create (123, "Hello");

Console.WriteLine (t1 == t2);           // False
Console.WriteLine (t1.Equals (t2));     // True

Guid Struct

The Guid struct represents a globally unique identifier: a 16-byte value when generated is almost unique in every way.

To instantiate an existing value, you use the constructors.

public Guid (byte[] b); //Accepts a 16-byte array
public Guid (string g); //Accepts a formatted string
//generate new unique Guid
Guid g = Guid.NewGuid ();
g.ToString().Dump ("Guid.NewGuid.ToString()");

Guid g1 = new Guid ("{0d57629c-7d6e-4847-97cb-9e2fc25083fe}");
Guid g2 = new Guid ("0d57629c7d6e484797cb9e2fc25083fe");
Console.WriteLine (g1 == g2);  // True

byte[] bytes = g.ToByteArray();
Guid g3 = new Guid (bytes);
g3.Dump();

Guid.Empty.Dump ("Guid.Empty");
default(Guid).Dump ("default(Guid)");
Guid.Empty.ToByteArray().Dump ("Guid.Empty - bytes");

C# – Working with Numbers

Working with Numbers

Conversions

Task Functions Examples
Parsing base 10 numbers Parse
TryParse
double d = double.Parse("3.5");
int i;
bool ok = int.TryParse("3", out i);
        
Parsing from base2, 8, or 16 Convert.ToIntegral
int i = Convert.ToInt32("1E", 16);
Formatting to hexadecimal ToString(“X”);
string hex = 45.ToString("X");
Lossless Numeric Conversion Implicit Cast
int i = 23;
double d = i;
Truncating Numeric Conversion Explicit Cast
double d = 23.5;
int i = (int) d;
Rounding numeric conversion (real to integral) Convert.ToIntegral
double d = 23.5;
int i = Convert.ToInt32(d);

Math

Lists the members of the static Math class.

Category Methods
Rounding Round, Truncate, Floor, Ceiling
Maximum/Minimum Max, Min
Absolute value and sign Abs, Sign
Square root Sqrt
Raising to a power Pow, Exp
Logarithm Log, Log10
Trigonometric Sin, Cos, Tan

Floor always rounds down, Ceiling always rounds up – even with negative numbers. If you have array or sequence of numbers, use Max and Min extension methods in System.Linq.Enumerable.

BigInteger

Lives in System.Numerics in namespace called System.Numerics.dll which allows you to represent an arbitrarily large integer without any loss of precision.

// BigInteger supports arbitrary precision.

BigInteger twentyFive = 25;      // implicit cast from integer

BigInteger googol = BigInteger.Pow (10, 100); 

// Alternatively, you can Parse a string: 
BigInteger googolFromString = BigInteger.Parse ("1".PadRight (100, '0'));

Console.WriteLine (googol.ToString());

double g1 = 1e100;                  // implicit cast
BigInteger g2 = (BigInteger) g1;    // explicit cast
g2.Dump ("Note loss of precision");

// This uses the System.Security.Cryptography namespace:
RandomNumberGenerator rand = RandomNumberGenerator.Create();
byte[] bytes = new byte [32];
rand.GetBytes (bytes);
var bigRandomNumber = new BigInteger (bytes);   // Convert to BigInteger
bigRandomNumber.Dump ("Big random number");

The advantage of storing such a number in a BigInteger over a byte is that you get value-type semantics. Calling ToByteArray converts a BigInteger back to a byte array.

Complex

Represents real and imaginary components of type double. Complex resides in the System.Numerics.dll assembly.

var c1 = new Complex (2, 3.5);
var c2 = new Complex (3, 0);

Console.WriteLine (c1.Real);       // 2
Console.WriteLine (c1.Imaginary);  // 3.5
Console.WriteLine (c1.Phase);      // 1.05165021254837
Console.WriteLine (c1.Magnitude);  // 4.03112887414927

Complex c3 = Complex.FromPolarCoordinates (1.3, 5);

// The standard arithmetic operators are overloaded to work on Complex numbers:
Console.WriteLine (c1 + c2);    // (5, 3.5)
Console.WriteLine (c1 * c2);    // (6, 10.5)

Complex.Atan (c1).Dump ("Atan");
Complex.Log10 (c1).Dump ("Log10");
Complex.Conjugate (c1).Dump ("Conjugate");

Random

The Random class generates a pseudorandom sequence of random bytes, integers or doubles. Before using Random, you need to instantiate first with or without seed.

Next(n) – generates a random integer between 0 and n-1.

NextDouble – generates a random double between 0 and 1.

NextByte – fills a byte array with random values.

Random is not considered for high-security applications instead cryptography should be used.

// If given the same seed, the random number series will be the same:
Random r1 = new Random (1);
Random r2 = new Random (1);
Console.WriteLine (r1.Next (100) + ", " + r1.Next (100));      // 24, 11
Console.WriteLine (r2.Next (100) + ", " + r2.Next (100));      // 24, 11

// Using system clock for seed:
Random r3 = new Random();
Random r4 = new Random();
Console.WriteLine (r3.Next (100) + ", " + r3.Next (100));      // ?, ?
Console.WriteLine (r4.Next (100) + ", " + r4.Next (100));      // ", "
// Notice we still get same sequences, because of limitations in system clock resolution.

// Here's a workaround:
Random r5 = new Random (Guid.NewGuid().GetHashCode());
Random r6 = new Random (Guid.NewGuid().GetHashCode());
Console.WriteLine (r5.Next (100) + ", " + r5.Next (100));      // ?, ?
Console.WriteLine (r6.Next (100) + ", " + r6.Next (100));      // ?, ?

// Random is not crytographically strong (the following, however, is):
var rand = System.Security.Cryptography.RandomNumberGenerator.Create();
byte[] bytes = new byte [4];
rand.GetBytes (bytes);       // Fill the byte array with random numbers.

BitConverter.ToInt32 (bytes, 0).Dump ("A cryptographically strong random integer");

C# – Formatting and Parsing

Formatting and Parsing

Formatting means converting to a string.

Parsing means converting from a string.

ToString

Gives meaningful output on all simple value types – bool, DateTime, DateTimeOffset, TimeSpan, Guid.

// The simplest formatting mechanism is the ToString method.
string s = true.ToString();

// Parse does the reverse:
bool b = bool.Parse (s);

Parse

FormatException is thrown when parsing fails.

TryParse method returns false if the conversation fails rather than throwing exception.

// TryParse avoids a FormatException in case of error:
int i;
int.TryParse ("qwerty", out i).Dump ("Successful");
int.TryParse ("123", out i).Dump ("Successful");

Parse and TryParse methods on DateTime(Offset) and the numeric types respect local culture settings. It can be changed by specifying a CultureInfo object.

// Culture trap:
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo ("de-DE");  // Germany
double.Parse ("1.234").Dump ("Parsing 1.234");   // 1234 

// Specifying invariant culture fixes this:
double.Parse ("1.234", CultureInfo.InvariantCulture).Dump ("Parsing 1.234 Invariantly");

(1.234).ToString ().Dump ("1.234.ToString()");
(1.234).ToString (CultureInfo.InvariantCulture).Dump ("1.234.ToString Invariant");

Format Providers

The gateway to using a format provider is IFormattable.

public interface IFormattable
{
//first argument is the format string
//second argument is the format provider  
string ToString (string format, IFormatProvider formatP{rovider);
}

The format string provides instructions and the format provider determines how the instructions are translated.

NumberFormatInfo f = new NumberFormatInfo();
f.CurrencySymbol = "$$";
Console.WriteLine (3.ToString ("C", f));          // $$ 3.00

// The default format provider is CultureInfo.CurrentCulture:
Console.WriteLine (10.3.ToString ("C", null));

// For convenience, most types overload ToString such that you can omit a null provider:
Console.WriteLine (10.3.ToString ("C"));
Console.WriteLine (10.3.ToString ("F4"));		// (Fix to 4 D.P.)

.NET Framework define three format providers: NumberFormatInfo, DateTimeFormatInfo, CultureInfo.

Format providers and CultureInfo

Returning the object applicable to the culture’s regional settings.

// Requesting a specific culture (english language in Great Britain):
CultureInfo uk = CultureInfo.GetCultureInfo ("en-GB");
Console.WriteLine (3.ToString ("C", uk));      // £3.00

Invariant culture is always the same, regardless of the computer’s settings. It is based on American culture.

// Invariant culture:
DateTime dt = new DateTime (2000, 1, 2);
CultureInfo iv = CultureInfo.InvariantCulture;
Console.WriteLine (dt.ToString (iv));            // 01/02/2000 00:00:00
Console.WriteLine (dt.ToString ("d", iv));       // 01/02/2000

Using NumberFormatInfo or DateTimeFormatInfo

The initial settings for a NumberFormatInfo or DateTimeFormatInfo are based on the invariant culture.

// Creating a custom NumberFormatInfo:
NumberFormatInfo f = new NumberFormatInfo ();
f.NumberGroupSeparator = " ";
Console.WriteLine (12345.6789.ToString ("N3", f));   // 12 345.679

// Cloning:
NumberFormatInfo f2 = (NumberFormatInfo) CultureInfo.CurrentCulture.NumberFormat.Clone();

// Now we can edit f2:
f2.NumberGroupSeparator = "*";
Console.WriteLine (12345.6789.ToString ("N3", f2));   // 12 345.679

Composite formatting

Allows you to combine variable substitution with format strings.

string composite = "Credit={0:C}";
Console.WriteLine (string.Format (composite, 500));   // Credit=$500.00

Console.WriteLine ("Credit={0:C}", 500);   // Credit=$500.00

{
	object someObject = DateTime.Now;
	string s = string.Format (CultureInfo.InvariantCulture, "{0}", someObject);
}
// Equivalent to:
{
	object someObject = DateTime.Now;
	string s;
	if (someObject is IFormattable)
		s = ((IFormattable)someObject).ToString (null, CultureInfo.InvariantCulture);
	else if (someObject == null)
		s = "";
	else
		s = someObject.ToString();
}

Parsing with format providers

NumberStyles and DateTimeStyles control how parsing work: they let you specify such things as whether parentheses or a currency symbol can appear in the input string.

// There’s no standard interface for parsing through a format provider; instead use Parse/TryParse methods on the target types:

try 
{
	int error = int.Parse ("(2)");   // Exception thrown
}
catch (FormatException ex) { ex.Dump(); }	

int minusTwo = int.Parse ("(2)", NumberStyles.Integer | NumberStyles.AllowParentheses);   // OK
minusTwo.Dump();

decimal fivePointTwo = decimal.Parse ("£5.20", NumberStyles.Currency, CultureInfo.GetCultureInfo ("en-GB"));
fivePointTwo.Dump();

IFormatProvider and ICustomFormatter

public interface IFormatProvider {object GetFormat (Type formatType); }

The purpose of this method is to provide indirection where it allows CultureInfo to defer to an appropriate NumberFormatInfo or DateTimeInfo object to do the work.

C# – Dates and Times

Dates and Times

Three structs can be used for dates and times:

1. DateTime

2. DateTimeOffset

3. TimeSpan

TimeSpan

Represents time of the day, simply the clock time with date missing.

Has a resolution of 100ns, maximum value of about 10 million days and it can be positive or negative.

The static Parse method does the opposite of ToString, converting a string to a TimeSpan.

TryParse does the same but returns false rather than throwing an exception if the conversion fails.

The XmlConvert class also provides TimeSpan string-conversion methods that follow standard XML formatting protocols.

The default value for a TimeSpan is TimeSpan.Zero.

To obtain the current time of day, call DateTime.Now.TimeOfDay.

Three ways to construct a TimeSpan:

1. Through one of the constructors

public TimeSpan (int hours, int minutes, int seconds);
public TimeSpan (int days, int hours, int minutes, int seconds);
public TimeSpan (int days, int hours, int minutes, int seconds, int milliseconds);
public TimeSpan (long ticks);

//example (hours, minutes, seconds)
Console.WriteLine(new TimeSpan(2,30,0));

2. By calling one of the static From… methods

More convenient when you want to specify an interval in just a single unit, such as minutes, hours.

public static TimeSpan FromDays (double value);
public static TimeSpan FromHours (double value);
public static TimeSpan FromMinutes (double value);
public static TimeSpan FromSeconds (double value);
public static TimeSpan FromMilliseconds (double value);
Console.WriteLine(TimeSpan.FromHours(2.5));
Console.WriteLine(TimeSpan.FromHours(-2.5));
Console.WriteLine(TimeSpan.FromHours(2) + TimeSpan.FromMinutes(30));
Console.WriteLine(TimeSpan.FromDays(10) - TimeSpan.FromSeconds(1));

TimeSpan nearlyTenDays = TimeSpan.FromDays(10) - TimeSpan.FromSeconds(1);
Console.WriteLine(nearlyTenDays.Days);
Console.WriteLine(nearlyTenDays.Hours);
Console.WriteLine(nearlyTenDays.Minutes);
Console.WriteLine(nearlyTenDays.Seconds);
Console.WriteLine(nearlyTenDays.Milliseconds);

Total… properties return values of type double.

Console.WriteLine(nearlyTenDays.TotalDays);
Console.WriteLine(nearlyTenDays.TotalHours);
Console.WriteLine(nearlyTenDays.TotalMinutes);
Console.WriteLine(nearlyTenDays.TotalSeconds);
Console.WriteLine(nearlyTenDays.TotalMilliseconds);

3. By subtracting one DateTime from another

Console.WriteLine (new TimeSpan (2, 30, 0));     // 02:30:00
Console.WriteLine (TimeSpan.FromHours (2.5));    // 02:30:00
Console.WriteLine (TimeSpan.FromHours (-2.5));   // -02:30:00
Console.WriteLine (DateTime.MaxValue - DateTime.MinValue);

// TimeSpan overloads the < and > operators, as well as the + and - operators:

(TimeSpan.FromHours(2) + TimeSpan.FromMinutes(30)).Dump ("2.5 hours");
(TimeSpan.FromDays(10) - TimeSpan.FromSeconds(1)).Dump ("One second short of 10 days");

DateTime and DateTimeOffset

DateTime and DateTimeOffset are immutable structs for representing a date and optionally a time with a resolution of 100ns and converting years range 0001 through 9999.

DateTimeOffset

It stores UTC offset, makes more meaningful when comparing values across different time zones.

July 01 2007 03:00:00 -06:00

Choosing between DateTime and DateTimeOffset

DateTime and DateTimeOffset differ how they handle time zones.

DateTime incorporates three-state flag:

  1. The local time on the computer
  2. UTC
  3. Unspecified

Equality Comparisons

DateTime ignore the three-state flag in comparisons and considers two values equal if they have the same year, month, hour, minute and so on.

DateTimeOffset considers two values equal if they refer to the same point in time.

Example

July 01 2007 09:00:00 +00:00 (GMT)
July 01 2007 03:00:00 -06:00 (local time, Central America)

According to DateTime the two values are different whereas DateTimeOffset considers them equal.

For example for international event dates, DateTimeOffset is suitable compared to DateTime where it requires standardizing on a single time zone throughout the application.

Constructing a DateTime

DateTime defines constructors that accept integers for the year, month, day and also optionally the hour, minute, second and millisecond.

public DateTime (int year, int month, int day);
public DateTime (int year, int month, int day,int hour, int minute, int second, int millisecond);

DateTime also incorporates DateTimeKind enum: Unspecified, Local, UTC.

Unspecified is default where DateTime is time-zone agnostic.

Local is local computer time zone.

DateTime d1 = new DateTime (2010, 1, 30);	// Midnight, January 30 2010

DateTime d2 = new DateTime (2010, 1, 30, 12, 0, 0);		// Midday, January 30 2010

DateTime d3 = new DateTime (2010, 1, 30, 12, 0, 0, DateTimeKind.Utc);

DateTimeOffset d4 = d1;		// Implicit conversion

DateTimeOffset d5 = new DateTimeOffset (d1, TimeSpan.FromHours (-8));	// -8 hours UTC

DateTime constructors also overload to accept Calendar object which enables you to specify a date using any of the Calendar subclasses defined in System.Globalization.

DateTime d = new DateTime (5767, 1, 1, new System.Globalization.HebrewCalendar());
Console.WriteLine (d);    // 12/12/2006 12:00:00 AM

You can also construct DateTime with a single ticks value of type long, where ticks is the number of 100 ns intervals from midnight 01/01/0001.

Converting

DateTime provides the static FromFileTime and FromFileTimeUtc methods for converting from windows file name and FromOADate for converting from OLE automation date/time.

To construct DateTime from string call the static Parse or ParseExact method.

Constructing a DateTimeOffset

public DateTimeOffset (int year, int month, int day,int hour, int minute, int second,
TimeSpan offset);
public DateTimeOffset (int year, int month, int day,int hour, int minute, int second, int millisecond,TimeSpan offset);

DateTimeOffset also has constructors that accept a Calendar object, a long ticks value, and static Parse and ParseExact methods that accept a string.

DateTimeOffset can be created from an existing DateTime either by using constructors or implicit cast.

//constructors
public DateTimeOffset (DateTime dateTime);
public DateTimeOffset (DateTime dateTime, TimeSpan offset);
//implict cast
DateTimeOffset dt = new DateTime(2000, 2, 3);

DateTimeOffset provides three properties that return values of type DateTime:

1. The UtcDateTime property returns a DateTime in UTC time.

2. The LocalDateTime property returns a DateTime in the current local time zone.

3. The DateTime property returns a DateTime in whatever zone it was specified, with a Kind of Unspecified.

The current DateTime/DateTimeOffset

Now property returns the current date and time.

//Now property
Console.WriteLine(DateTime.Now);
Console.WriteLine(DateTimeOffset.Now);

//Today property
Console.WriteLine(DateTime.Today);

//Utc now property
Console.WriteLine(DateTime.UtcNow);
Console.WriteLine(DateTimeOffset.UtcNow);

Working with dates and times

DateTime dt = new DateTime (2000, 2, 3,	10, 20, 30);

Console.WriteLine (dt.Year);         // 2000
Console.WriteLine (dt.Month);        // 2
Console.WriteLine (dt.Day);          // 3
Console.WriteLine (dt.DayOfWeek);    // Thursday
Console.WriteLine (dt.DayOfYear);    // 34

Console.WriteLine (dt.Hour);         // 10
Console.WriteLine (dt.Minute);       // 20
Console.WriteLine (dt.Second);       // 30
Console.WriteLine (dt.Millisecond);  // 0
Console.WriteLine (dt.Ticks);        // 630851700300000000
Console.WriteLine (dt.TimeOfDay);    // 10:20:30  (returns a TimeSpan)

TimeSpan ts = TimeSpan.FromMinutes (90);
Console.WriteLine (dt.Add (ts));         // 3/02/2000 11:50:30 AM
Console.WriteLine (dt + ts);             // 3/02/2000 11:50:30 AM

DateTime thisYear = new DateTime (2007, 1, 1);
DateTime nextYear = thisYear.AddYears (1);
TimeSpan oneYear = nextYear - thisYear;

Computations

AddYears AddMonths AddDays AddHours AddMinutes AddSeconds AddMilliseconds AddTicks

Formatting and parsing

DateTime.Now.ToString().Dump ("Short date followed by long time");
DateTimeOffset.Now.ToString().Dump ("Short date followed by long time (+ timezone)");

DateTime.Now.ToShortDateString().Dump ("ToShortDateString");
DateTime.Now.ToShortTimeString().Dump ("ToShortTimeString");

DateTime.Now.ToLongDateString().Dump ("ToLongDateString");
DateTime.Now.ToLongTimeString().Dump ("ToLongTimeString");

// Culture-agnostic methods make for reliable formatting & parsing:

DateTime dt1 = DateTime.Now;
string cannotBeMisparsed = dt1.ToString ("o");
DateTime dt2 = DateTime.Parse (cannotBeMisparsed);

Null DateTime and DateTimeOffset values

Since DateTime and DateTimeOffset are structs they are not nullable.

Use Nullable type (DateTime? or DateTimeOffset?)

Use static field DateTime.MinValue or DateTimeOffset.MinValue

Dates and Time Zones

DateTime and Time Zones

DateTime uses two piece of information: 62-bit number, indicating the number of ticks since since 1/1/0001 and 2-bit enum, indicating the DateTimeKind (Unspecified, Local, or Utc).

// When you compare two DateTime instances, only their ticks values are compared; their DateTimeKinds are ignored:

DateTime dt1 = new DateTime (2000, 1, 1, 10, 20, 30, DateTimeKind.Local);
DateTime dt2 = new DateTime (2000, 1, 1, 10, 20, 30, DateTimeKind.Utc);
Console.WriteLine (dt1 == dt2);          // True

DateTime local = DateTime.Now;
DateTime utc = local.ToUniversalTime();
Console.WriteLine (local == utc);        // False

// You can construct a DateTime that differs from another only in Kind with the static DateTime.SpecifyKind method:

DateTime d = new DateTime (2000, 12, 12);  // Unspecified
DateTime utc2 = DateTime.SpecifyKind (d, DateTimeKind.Utc);
Console.WriteLine (utc2);            // 12/12/2000 12:00:00 AM

ToUniversalTime/ToLocalTime – converts to universal/local time. It applies the computer’s current time zone settings and returns a new DateTime with a DateTimeKind or Utc or Local.

No conversion happens if you call ToUniversalTime on a DateTime that’s already Utc or ToLocalTime that’s already Local.

DateTimeOffset and TimeZones

DateTimeOffset comprises a DateTime field whose value is always in UTC and a 16-bit integer field for the UTC offset in minutes.

DateTimeOffset local = DateTimeOffset.Now;
DateTimeOffset utc = local.ToUniversalTime();
            
Console.WriteLine (local.Offset);
Console.WriteLine (utc.Offset);

TimeZone and TimeZoneInfo

TimeZone lets you access only the current local time zone whereas TimeZoneInfo provides access to all the world’s time zones.

TimeZone

The static TimeZone.CurrentTimeZone method returns a TimeZone object based on the current local settings.

TimeZone zone = TimeZone.CurrentTimeZone;
Console.WriteLine(zone.StandardName);
Console.WriteLine(zone.DaylightName);

IsDaylightSavingTime and GetUtcOffset methods

DateTime dt1 = new DateTime (2018, 1, 1);
DateTime dt2 = new DateTime (2018, 6, 1);
            
Console.WriteLine (zone.IsDaylightSavingTime(dt1));
Console.WriteLine (zone.IsDaylightSavingTime(dt2));
Console.WriteLine (zone.GetUtcOffset(dt1));
Console.WriteLine (zone.GetUtcOffset(dt2));

The GetDaylightChanges method returns specific daylight saving time information for a given year

DaylightTime day = zone.GetDaylightChanges(2018);
Console.WriteLine (day.Start.ToString("M"));
Console.WriteLine (day.End.ToString("M"));
Console.WriteLine (day.Delta);

TimeZoneInfo

TimeZoneInfo.Local returns the current local time zone.

TimeZoneInfo zone = TimeZoneInfo.Local;
Console.WriteLine (zone.StandardName);
Console.WriteLine (zone.DaylightName);

Also provides IsDaylightSavingTime and GetUtcOffset methods only difference is that they accept either a DateTime or a DateTimeOffset.

FindSystemTimeZoneById with the zone Id you obtain TimeZoneInfo of the world’s time zones.

TimeZoneInfo wa = TimeZoneInfo.FindSystemTimeZoneById ("W. Australia Standard Time");

Console.WriteLine (wa.Id);                   // W. Australia Standard Time
Console.WriteLine (wa.DisplayName);          // (GMT+08:00) Perth
Console.WriteLine (wa.BaseUtcOffset);        // 08:00:00
Console.WriteLine (wa.SupportsDaylightSavingTime);     // True

GetSystemTimeZones method returns all world time zones.

foreach (TimeZoneInfo z in TimeZoneInfo.GetSystemTimeZones())
     Console.WriteLine(z.Id);

ConvertTime method converts a DateTime or DateTimeOffset from one time zone to another.

ConvertTimeFromUtc and ConvertTimeToUtc methods can be used to convert from or to UTC.

Daylight Saving Time

TimeZoneInfo provides the following additional methods:

IsValidTime returns true if a DateTime is within the hour that’s skipped when the clocks move forward.

IsAmbiguousTime returns true if a DateTime or DateTimeoffset is within the hour that’s repeated when the clock move back.

GetAmbiguousTimeOffsets returns an array of TimeSpans representing the valid offset choices for an ambiguous DateTime or DateTimeOffset.

GetAdjustmentRules, which returns a declarative summary of all daylight saving rules that apply to all years. Each rule has a DateStart and DateEnd indicating the date range within which the rule is valid.

// Western Australia's daylight saving rules are interesting, having introduced daylight
// saving midseason in 2006 (and then subsequently rescinding it):
	
TimeZoneInfo wa = TimeZoneInfo.FindSystemTimeZoneById ("W. Australia Standard Time");
	
foreach (TimeZoneInfo.AdjustmentRule rule in wa.GetAdjustmentRules())
	Console.WriteLine ("Rule: applies from " + rule.DateStart +
                                            " to " + rule.DateEnd);
	
foreach (TimeZoneInfo.AdjustmentRule rule in wa.GetAdjustmentRules())
   {
     Console.WriteLine();
     Console.WriteLine ("Rule: applies from " + rule.DateStart + " to " + rule.DateEnd);
     Console.WriteLine ("   Delta: " + rule.DaylightDelta);
     Console.WriteLine ("   Start: " + FormatTransitionTime (rule.DaylightTransitionStart, false));
     Console.WriteLine ("   End:   " + FormatTransitionTime (rule.DaylightTransitionEnd, true));
   }

AdjustmentRule

Has a DaylightDelta property of type TimeSpan and properties called DaylightTransitionStart and DaylightTransitionEnd.

Properties of TimeZoneInfo.TransitionTime:

public bool IsFixedDateRule { get; }
public DayOfWeek DayOfWeek { get; }
public int Week { get; }
public int Day { get; }
public int Month { get; }
public DateTime TimeOfDay { get; }

Daylight Saving Time and DateTime

// The IsDaylightSavingTime tells you whether a given local DateTime is subject to daylight saving.
Console.WriteLine (DateTime.Now.IsDaylightSavingTime());     // True or False

// UTC times always return false:
Console.WriteLine (DateTime.UtcNow.IsDaylightSavingTime());  // Always False

// The end of daylight saving presents a particular complication for algorithms that use local time.
// The comments on the right show the results of running this in a daylight-saving-enabled zone:
DaylightTime changes = TimeZone.CurrentTimeZone.GetDaylightChanges (2010);
TimeSpan halfDelta = new TimeSpan (changes.Delta.Ticks / 2);
DateTime utc1 = changes.End.ToUniversalTime() - halfDelta;
DateTime utc2 = utc1 - changes.Delta;

// Converting these variables to local times demonstrates why you should use UTC and not local time
// if your code relies on time moving forward:
DateTime loc1 = utc1.ToLocalTime();  // (Pacific Standard Time)
DateTime loc2 = utc2.ToLocalTime();
Console.WriteLine (loc1);            // 2/11/2010 1:30:00 AM
Console.WriteLine (loc2);            // 2/11/2010 1:30:00 AM
Console.WriteLine (loc1 == loc2);    // True

// Despite loc1 and loc2 reporting as equal, they are different inside:
Console.WriteLine (loc1.ToString ("o"));  // 2010-11-02T02:30:00.0000000-08:00
Console.WriteLine (loc2.ToString ("o"));  // 2010-11-02T02:30:00.0000000-07:00

// The extra bit ensures correct round-tripping between local and UTC times:
Console.WriteLine (loc1.ToUniversalTime() == utc1);   // True
Console.WriteLine (loc2.ToUniversalTime() == utc2);   // True

C# – String Builder

String Builder

System.Text represents a mutable string. Most popular usage of string builder is to concatenate strings. To clear contents of string builder, instantiate or set its length to zero.

Functions

Method Description
Append
Insert
Remove
Replace
AppendLine Appends and adds a newline sequence
AppendFormat Accepts composite format string
Length Length of string
StringBuilder sb = new StringBuilder();

for (int i = 0; i < 50; i++) sb.Append (i + ",");

// To get the final result, call ToString():
Console.WriteLine (sb.ToString());

sb.Remove (0, 60);		// Remove first 50 characters
sb.Length = 10;			// Truncate to 10 characters
sb.Replace (",", "+");	// Replace comma with +
sb.ToString().Dump();

sb.Length = 0;			// Clear StringBuilder

Text Encodings and Unicode

A character set is an allocation of characters each with a numeric code or code point.

Unicode

Has address space of approx 1 million characters.

Covers most spoken world languages, historical languages and special symbols.

ASCII

Set of first 128 characters of the Unicode set.

Text Encoding

Maps characters from their numeric code point to a binary representation.

In .Net text encoding is important when dealing with text files or streams.

A text encoding can restrict what characters can be represented as well as impacting storage efficiency.

Two categories of text encoding in .NET:

1. Map Unicode characters to another character set

2. Use standard Unicode encoding schemes

Obtaining an Encoding Object

The Encoding class in System.Text is the common base type for classes that encapsulate text encoding.

The easiest way to instantiate a correctly configured class is to call Encoding.GetEncoding.

Encoding Name Static Property on Encoding
UTF-8 Encoding.UTF8
UTF-16 Encoding.Unicode
UTF-32 Encoding.UTF32
ASCII Encoding.ASCII
// The easiest way to instantiate a correctly configured encoding class is to 
// call Encoding.GetEncoding with a standard IANA name:

Encoding utf8 = Encoding.GetEncoding ("utf-8");
Encoding chinese = Encoding.GetEncoding ("GB18030");

utf8.Dump();
chinese.Dump();

// The static GetEncodings method returns a list of all supported encodings:
foreach (EncodingInfo info in Encoding.GetEncodings())
	Console.WriteLine (info.Name);

Encoding for file and stream I/O

The Encoding object controls how text is read and written to a file or stream. UTF-8 is the default text encoding for all file and stream I/O.

System.IO.File.WriteAllText ("data.txt", "Testing...", Encoding.Unicode);
//writes Testing... to  file called data.txt in UTF-16 encoding

Encoding to byte arrays

Encoding object can be used to and from a byte array. The GetBytes method converts from string to byte[] with the given encoding. The GetString converts from byte[] to string.

byte[] utf8Bytes  = System.Text.Encoding.UTF8.GetBytes    ("0123456789");
byte[] utf16Bytes = System.Text.Encoding.Unicode.GetBytes ("0123456789");
byte[] utf32Bytes = System.Text.Encoding.UTF32.GetBytes   ("0123456789");

Console.WriteLine (utf8Bytes.Length);    // 10
Console.WriteLine (utf16Bytes.Length);   // 20
Console.WriteLine (utf32Bytes.Length);   // 40

string original1 = System.Text.Encoding.UTF8.GetString    (utf8Bytes);
string original2 = System.Text.Encoding.Unicode.GetString (utf16Bytes);
string original3 = System.Text.Encoding.UTF32.GetString   (utf32Bytes);

Console.WriteLine (original1);          // 0123456789
Console.WriteLine (original2);          // 0123456789
Console.WriteLine (original3);          // 0123456789

UTF-16 and surrogate pairs

A string’s Length property may be greater than its real character count.

A single char is not always enough to fully represent a Unicode character.

Two-word characters are called surrogates.

Methods to check for surrogates:

bool IsSurrogate (char c)
bool IsHighSurrogate (char c)
bool IsLowSurrogate (char c)
bool IsSurrogatePair (char highSurrogate, char lowSurrogate)

The StringInfo class in the System.Globalization namespace also provides a range of methods and properties for working with two-word characters.

int musicalNote = 0x1D161;

string s = char.ConvertFromUtf32 (musicalNote);
s.Length.Dump();	// 2 (surrogate pair)

char.ConvertToUtf32 (s, 0).ToString ("X").Dump();			// Consumes two chars
char.ConvertToUtf32 (s[0], s[1]).ToString ("X").Dump();		// Explicitly specify two chars

C# – String and Text Handling

String and Text Handling

Char

char represent a single Unicode character and alias the System.Char struct.

// char literals:
char c = 'A';
char newLine = '\n';

System.Char defines a range of static methods for working with characters

Console.WriteLine (char.ToUpper ('c'));				// C
Console.WriteLine (char.IsWhiteSpace ('\t'));		// True
Console.WriteLine (char.IsLetter ('x'));			// True
Console.WriteLine (char.GetUnicodeCategory ('x'));	// LowercaseLetter

ToUpper and ToLower honor the end-user’s locale, which can lead to subtle bugs. This applies to both char and string.

Turkey Example:

// To illustrate, let's pretend we live in Turkey:
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo ("tr-TR");

// The following expression evaluates to false:
Console.WriteLine (char.ToUpper ('i') == 'I');

// Let's see why:
Console.WriteLine (char.ToUpper ('i'));   // İ

To avoid this problem System.Char and System.String provides culture-invariant version.

// In contrast, the *Invariant methods always apply the same culture:
Console.WriteLine (char.ToUpperInvariant ('i'));			// I
Console.WriteLine (char.ToUpperInvariant ('i') == 'I');		// True

//shorthand
Console.WriteLine(char.ToUpper('i', CultureInfo.InvariantCulture));

Static methods for categorizing characters

Static Method Characters Included Unicode
IsLetter A–Z, a–z, and letters of other alphabets UpperCaseLetter
LowerCaseLetter
TitleCaseLetter
ModifierLetter
OtherLetter
IsUpper Uppercase letters UpperCaseLetter
IsLower Lowercase letters LowerCaseLetter
IsDigit 0–9 plus digits of other alphabets DecimalDigitNumber
IsLetterOrDigit Letters plus digits (IsLetter, IsDigit)
IsNumber All digits plus Unicode fractions and Roman numeral symbols DecimalDigitNumber
LetterNumber
OtherNumber
IsSeparator Space plus all Unicode separator characters LineSeparator
ParagraphSeparator
IsWhiteSpace All separators plus \n, \r, \t, \f, and \v LineSeparator
ParagraphSeparator
IsPunctuation Symbols used for punctuation in Western and other alphabets DashPunctuation
ConnectorPunctuation
InitialQuotePunctuation
FinalQuotePunctuation
IsSymbol Most other printable symbols MathSymbol
ModifierSymbol
OtherSymbol
IsControl Nonprintable “control” characters below 0x20, such as \r, \n, \t, \0, and characters between 0x7F and 0x9A (None)

String

Related Article

C# string is an immutable sequence of characters.

Constructing strings

// String literals:
string s1 = "Hello";
string s2 = "First Line\r\nSecond Line";
string s3 = @"\\server\fileshare\helloworld.cs";

// To create a repeating sequence of characters you can use string’s constructor:
Console.Write (new string ('*', 10));    // **********

// You can also construct a string from a char array. ToCharArray does the reverse:
char[] ca = "Hello".ToCharArray();
string s = new string (ca);              // s = "Hello"
s.Dump();

Null and empty strings

An empty string has a length of zero, to create empty string you can use literal or static field: string.Empty. For testing empty string you can use Length property or equality comparison.

string empty = "";
Console.WriteLine (empty == "");              // True
Console.WriteLine (empty == string.Empty);    // True
Console.WriteLine (empty.Length == 0);        // True

Since strings are reference types they can be null also:

string nullString = null;
Console.WriteLine (nullString == null);        // True
Console.WriteLine (nullString == "");          // False
Console.WriteLine (string.IsNullOrEmpty (nullString));	// True
Console.WriteLine (nullString.Length == 0);             // NullReferenceException

static string.IsNullOrEmpty method is useful to test whether string is null or empty.

Accessing characters within a string

A string’s indexer returns a single character at the given index.

string str  = "abcde";
char letter = str[1];        // letter == 'b'

// string also implements IEnumerable, so you can foreach over its characters:
foreach (char c in "123") Console.Write (c + ",");    // 1,2,3,

Searching within strings

StartsWith Returns boolean result and overloaded with StringComparison enum or CultureInfo object
EndsWith Returns boolean result and overloaded with StringComparison enum or CultureInfo object
Contains Search for a word within a string
IndexOf Returns the position of a given character or -1 if not found.
// The simplest search methods are Contains, StartsWith, and EndsWith:
Console.WriteLine ("quick brown fox".Contains ("brown"));    // True
Console.WriteLine ("quick brown fox".EndsWith ("fox"));      // True

// LastIndexOf is like IndexOf, but works backward through the string.
// IndexOfAny returns the first matching position of any one of a set of characters:
Console.WriteLine ("ab,cd ef".IndexOfAny (new char[] {' ', ','} ));       // 2
Console.WriteLine ("pas5w0rd".IndexOfAny ("0123456789".ToCharArray() ));  // 3

// LastIndexOfAny does the same in the reverse direction.

// IndexOf is overloaded to accept a startPosition StringComparison enum, which enables case-insensitive searches:
Console.WriteLine ("abcde".IndexOf ("CD", StringComparison.CurrentCultureIgnoreCase));    // 2

Manipulating Strings

String is immutable thus all the manipulated string return a new one.

Substrings(Int index, int length) Extract portion of the string, length is optional.
Insert(int index, string char) Insert string at the specified index position.
Remove(int32 index, int32 count) Remove characters from string based on the specified position and count the number of characters, count is option
Padleft(int totalwidth, paddingChar) Return a right-align string of specified length, with or without padding characters.
PadRight(int totalwidth, paddingChar) Return a left-align string of specified length, with or without padding characters.
TrimStart(char[]) Remove specified characters from the beginning of a string.
TrimEnd(char[]) Remove specified characters from the end of a string.
Trim() Removes all leading and trailing white-space characters.
Replace() Replaces all nonoverlapping occurrences of a particular character or string
ToUpper() Return uppercase of the string.
ToLower() Return lowercase of the string.

Splitting and joining strings

Split() Divides the string based on the delimiter specified, overloaded to accept params and also accepts StringSplitOptions enum. StringSplitOptions has an option to remove empty entries useful when words are seperated by several delimiters in a row.
Join() Joins set of string, it requires a delimiter and string array.
Concat() Joining set of strings but accepts params of string and has no separator.
// Split takes a sentence and returns an array of words (default delimiters = whitespace):
string[] words = "The quick brown fox".Split();
words.Dump();
  
// The static Join method does the reverse of Split:
string together = string.Join (" ", words);
together.Dump();								// The quick brown fox

// The static Concat method accepts only a params string array and applies no separator.
// This is exactly equivalent to the + operator:
string sentence     = string.Concat ("The", " quick", " brown", " fox");
string sameSentence = "The" + " quick" + " brown" + " fox";

sameSentence.Dump();		// The quick brown fox

String.Format and composite format strings

String.Format is used for building strings that embeds variables.

When calling String.Format you provide composite format string.

// When calling String.Format, provide a composite format string followed by each of the embedded variables
string composite = "It's {0} degrees in {1} on this {2} morning";
string s = string.Format (composite, 35, "Perth", DateTime.Now.DayOfWeek);
s.Dump();

// The minimum width in a format string is useful for aligning columns.
// If the value is negative, the data is left-aligned; otherwise, it’s right-aligned:
composite = "Name={0,-20} Credit Limit={1,15:C}";

Console.WriteLine (string.Format (composite, "Mary", 500));
Console.WriteLine (string.Format (composite, "Elizabeth", 20000));

// The equivalent without using string.Format:
s = "Name=" + "Mary".PadRight (20) + " Credit Limit=" + 500.ToString ("C").PadLeft (15);
s.Dump();

From C# 6, you can use interpolated string literals, just precede with $ symbol.

string s = $"It's hot this {DateTime.Now.DayOfWeek} morning";

Comparing Strings
Equality Comparisons

Compares that two instances are semantically same.

For string-equality you can use == operator or strings Equals method.

Order Comparisons

Tests which two instances comes first when arranging in ascending or descending order.

For string order comparisons you cna use CompareTo instance method or static Compare and CompareOrdinal methods.

Ordinal versus culture comparison
Ordinal

Interpret characters simply as numbers based on numeric Unicode value.

Culture

Interpret characters with reference to a particular alphabet.

2 Types of Cultures

1. Current Culture – settings picked up from the computer’s control panel.

2. Invariant Culture – same on every computer closely matches American culture.

Both culture types are useful for equality comparisons.

Culture-specific comparison is nearly always preferable.

String equality comparison

string == performs ordinal casesensitive comparison and also for string.Equals when it is called without parameter.

public enum StringComparison
{
	CurrentCulture,
	CurrentCultureIgnoreCase,
	InvariantCulture,
	InvariantCultureIgnoreCase,
	Ordinal,
	OrdinalIgnoreCase
}

String-order comparison

String’s CompareTo instance method performs culture-sensitive, case-sensitive order
comparison.

// String comparisons can be ordinal vs culture-sensitive; case-sensitive vs case-insensitive.

Console.WriteLine (string.Equals ("foo", "FOO", StringComparison.OrdinalIgnoreCase));   // True

// (The following symbols may not be displayed correctly, depending on your font):
Console.WriteLine ("ṻ" == "ǖ");   // False

// The order comparison methods return a positive number, a negative number, or zero, depending
// on whether the first value comes after, before, or alongside the second value:
Console.WriteLine ("Boston".CompareTo ("Austin"));    // 1
Console.WriteLine ("Boston".CompareTo ("Boston"));    // 0
Console.WriteLine ("Boston".CompareTo ("Chicago"));   // -1
Console.WriteLine ("ṻ".CompareTo ("ǖ"));              // 0
Console.WriteLine ("foo".CompareTo ("FOO"));          // -1

// The following performs a case-insensitive comparison using the current culture:
Console.WriteLine (string.Compare ("foo", "FOO", true));   // 0

// By supplying a CultureInfo object, you can plug in any alphabet:
CultureInfo german = CultureInfo.GetCultureInfo ("de-DE");
int i = string.Compare ("Müller", "Muller", false, german);
i.Dump();	// 1