The IEnumerable and IEnumerator Interfaces
Any type supporting a method names GetEnumerator() can be evaluated by the foreach construct.
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
The GetEnumerator() method returns a reference to yet another interface named System.Collections.IEnumerator. This interface provides the infrastructure to allow the caller to traverse the internal objects contained by the IEnumerable-compatible container.
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
class Garage : IEnumerable
{
private Car[] carArray = new Car[2];
public Garage()
{
carArray[0] = new Car("FeeFee", 200);
carArray[1] = new Car("Clunker", 90);
}
public IEnumerator GetEnumerator()
{
return carArray.GetEnumerator();
}
}
Garage carLot = new Garage();
IEnumerator carEnumerator = carLot.GetEnumerator();
Car myCar = (Car)carEnumerator.Current;
Building Iterator Methods with the yield Keyword
Iterator is a member that specifies how a container's internal items should be returned when processed by foreach.
public IEnumerator GetEnumerator()
{
foreach(Car c in carArray)
{
yield return c;
}
}
The yield keyword is used to specify the value (or values) to be returned to the caller's foreach construct. When the yield return statement is reached the current location in the container is stored, and execution is restarted from this location the next time the iterator is called.
Building a Named Iterator
It's also interesting to note that the "yield" keyword can technically be used within any method, regardless of its name. These methods (which one technically called named iterators) are also unique in that they can take any number of arguments. When building a named iterator be aware that the method will return the IEnumerable interface, rather than the expected IEnumerator-compatible type.
The ICloneable Interface
This method is used to obtain a "shallow copy" of the current object. Object users do not call this method directly, as it is protected. However, a given object may call this method itself during the cloning process.
public class Point
{
public int X { get; set; }
public int Y { get; set; }
public Point()
{
}
public Point(int xPos,int yPos)
{
X = xPos;
Y = yPos;
}
public override string ToString()
{
return $"{X} {Y}";
}
}
Point p1 = new Point(4,5);
Point p2 = p1;
p1.X = 0;
We have 2 objects that references to the same object in the memory.
When you want to give your custom type the ability to return an identical copy of itself to the caller, you may implement the standard ICloneable interface.
public class Point : ICloneable
{
public object Clone()
{
return new Point(this.X, this.Y);
}
}
Point p3 = new Point(100, 100);
Point p4 = (Point)p3.Clone();
public class Point : ICloneable
{
public object Clone()
{
return this.MemberwiseClone();
}
}The IComparable Interface
The System.IComparable interface specifies a behavior that allows an object to be stored based on same specified key.
public interface IComparable<in T>
{
int CompareTo(T? other);
}
Note: The generic version of this interface (IComparable<T>) provides a more type-safe manner to handle compersions between objects.
class Car : IComparable
{
public int CarId { get; set; }
public int CompareTo(object? obj)
{
if (obj is Car temp)
{
if (this.CarId > temp.CarId)
{
return 1;
}
if (this.CarId < temp.CarId)
{
return -1;
}
return 0;
}
throw new ArgumentException("Parameter is not Car!");
}
}The behind logic of CompareTo() is to test the incoming object against the current instance based on a specific point of data. The return value of CompareTo() is used to discover whether this type is less than, greater than, or equal to the object it is being compared with.
- Any number less than zero: This instance comes before the specified object in the sort order.
- Zero : This instance is equal to the specified object.
- Any number greater than zero : This instance comes after the specified object in the sort order.
Specifying Multiple Sort Orders with IComparer
public interface IComparer
{
int Compare(object? x, object? y);
}
The generic version of this interface IComparer<T> provides a more type-safe manner to handle comparisons between objects.
Unlike the IComparable interface IComparer is typically not implemented on the type you are trying to sort. Rather, you implement this interface on any number of helper classes, one for each sort order.
public class PetNameComparer : IComparer
{
public int Compare(object? x, object? y)
{
if (x is Car t1 && y is Car t2)
{
return string.Compare(t1.PetName, t2.PetName, StringComparison.OrdinalIgnoreCase);
}else
{
throw new ArgumentException("Parameter is not a Car!");
}
}
}