User Tools

Site Tools


notes:csharp:struct

C# struct

Examples of struct declarations. Note that the compiler automatically adds a default ctor to the struct that sets all fields to their default values:

// Example #1
struct TestStruct
{
    public int A;
    public int B;
 
    public TestStruct(int a, int b)
    {
        this.A = a;
        this.B = b;
    }
}
 
// Example #2 - when using automatically-implemented properties, use this() to initialize fields
struct TestStruct
{
    // Automatically-implemented properties.
    public int A { get; private set; }
    public int B { get; private set; }
 
    // Chain the custom ctor back to the compiler-generated default ctor.
    public TestStruct(int a, int b) : this()
    {
        this.A = a;
        this.B = b;
    }
}
 
// Example #3 - use this() to initialize fields
struct TestStruct
{
    private int _a;
    private int _b;
 
    // Manually-implemented properties with backing fields.
    public int A { get { return _a; } set { _a = value; } }
    public int B { get { return _b; } set { _b = value; } }
 
    public TestStruct(int a, int b) : this()
    {
        this.A = a;
        this.B = b;
    }
}
 
// Example #4 - initialize the backing fields in ctor rather than with this().
struct TestStruct
{
    private int _a;
    private int _b;
 
    // Manually-implemented properties with backing fields.
    public int A { get { return _a; } set { _a = value; } }
    public int B { get { return _b; } set { _b = value; } }
 
    public TestStruct(int a, int b)
    {
        // Initialize the backing fields manually.
        this._a = a;
        this._b = b;
    }
}

structs and interfaces

[Source: “Effective C#” by Bill Wagner]

You can save an unboxing penalty for structs by using interfaces. When you place a struct in a box, the box supports all interfaces that the struct supports. When you access the struct through the interface, you don't have to unbox the struct to access the object.

Example: Implement IComparable<T> and IComparable on a UrlInfo struct. This allows us to create a sorted list of UrlInfo objects. Note that even code that relies on the classic IComparable will need boxing and unboxing less often because the client can call IComparable.CompareTo() without unboxing the object.

public struct UrlInfo : IComparable<UrlInfo>, IComparable
{
    private Uri _url;
    private string _desc;
 
    // Compare the string representstion of the URL.
    public int CompareTo(UrlInfo other) => _url.ToString().CompareTo(other.Url.ToString());
 
    int IComparable.CompareTo(object obj) =>
        // Pattern-matching expression tests whether obj is a UrlInfo; if it is, it assigns obj 
        // to the variable other; otherwise, an exception is thrown
        (obj is UrlInfo other) ?
            CompareTo(other) :
            // A throw expression (C# 7) - throw no longer needs to be a separate statement.
            throw new ArgumentException(
                message: "Compared object is not UrlInfo",
                paramName: nameof(obj));
}

Example: A struct with overloaded operators:

public struct Vector2D
{
    public float X, Y;
 
    // The compiler automatically adds a default ctor to the struct 
    // that sets all fields to their default values.
 
    public Vector2D(float x, float y)
    {
        this.X = x;
        this.Y = y;
    }
 
    public string ShowComponents()
    {
        return "(" + this.X + "," + this.Y + ")";
    }
 
    // Length of the vector.
    public static float Length(Vector2D v)
    {
        return (float)Math.Sqrt(v.X * v.X + v.Y * v.Y);
    }
    public float Length()
    {
        return Length(this);
    }
 
    // Unary minus - an opposite vector.
    public static Vector2D operator -(Vector2D v)
    {
        return new Vector2D(-v.X, -v.Y);
    }
 
    // Adding vectors.
    public static Vector2D Add(Vector2D v1, Vector2D v2)
    {
        return new Vector2D(v1.X + v2.X, v1.Y + v2.Y);
    }
    public void Add(Vector2D v)
    {
        Add(this, v);
    }
 
    // Subtracting vectors.
    public static Vector2D Sub(Vector2D v1, Vector2D v2)
    {
        return new Vector2D(v1.X - v2.X, v1.Y - v2.Y);
    }
    public void Sub(Vector2D v)
    {
        Add(this, -v);
    }
 
    // Good practice: 
    // have the overloaded operators call the member function alternatives.
 
    // Overloading the "+" operator.
    public static Vector2D operator +(Vector2D v1, Vector2D v2)
    {
        return Add(v1, v2);
    }
 
    // Overloading the "-" operator.
    public static Vector2D operator -(Vector2D v1, Vector2D v2)
    {
        return Sub(v1, v2);
    }
 
    // Multiply by a scalar
    public static Vector2D Multiply(Vector2D v, float f)
    {
        return new Vector2D(f * v.X, f * v.Y);
    }
    public void Multiply(float f)
    {
        Multiply(this, f);
    }
 
    // We need two versions of the multiply operator because multiplication is commutative.
    public static Vector2D operator *(Vector2D v, float f)
    {
        return Multiply(v, f);
    }
    public static Vector2D operator *(float f, Vector2D v)
    {
        return Multiply(v, f);
    }
 
    // Overload the equality operators (== and !=) by overriding the System.Object.Equals() 
    // and System.Object.GetHashCode() methods and calling these methods from within 
    // "operator ==" and "operator !=".
    // Remarks: If you overloaded the "==" operator you *must* also override the "!=" operator.
    // Good practice: Classes that override Object.Equals() should always overload the == and != operators.
 
    // Override System.Object.Equals()
    public override bool Equals(object o)
    {
        if (((Vector2D)o).X == this.X &&
            ((Vector2D)o).Y == this.Y)
            return true;
        else
            return false;
    }
 
    // Override System.Object.GetHashCode()
    public override int GetHashCode()
    {
        return this.ToString().GetHashCode();
    }
 
    // Overload the "==" operator.
    // We simply call the implementation of
    // the overridden Equals() method.
    public static bool operator ==(Vector2D v1, Vector2D v2)
    {
        return v1.Equals(v2);
    }
 
    // Overload the "!=" operator.
    // If you overloaded the "==" operator you must
    // also override the "!=" operator.
    public static bool operator !=(Vector2D v1, Vector2D v2)
    {
        return !v1.Equals(v2);
    }
}
notes/csharp/struct.txt · Last modified: 2020/10/03 by leszek