Partager via


Common functional operations implemented in C#

When coding in OCaml (one of my favorite languages) there are a few things available that i find myself missing in C#. These include free structural equality and an automatic printed form. Structural equality

For example, given the following definition of a list:

 
# type 'a list = Nil | Cons of 'a * 'a list;;
type 'a list = Nil | Cons of 'a * 'a list

# let list1 = Cons(1, Cons(2, Nil));;
val list1 : int list = Cons (1, Cons (2, Nil))

# let list2 = Cons(2, Cons(3, Cons(4, Nil)));;
val list2 : int list = Cons (2, Cons (3, Cons (4, Nil)))

# let list3 = Cons(1, Cons(2, Nil));;
val list3 : int list = Cons (1, Cons (2, Nil))

# list1 = list2;;
- : bool = false

# list1 = list3;;
- : bool = true

# list3;;
- : int list = Cons (1, Cons (2, Nil))

Here we that i get the structural equality `=` operator for free, and if I just want the value of the list printed in a simple format I get that as well. Note: if i want physical equality (i.e. the two objects are at the same memory address) i can still get that through the `==` operator.

A while back Luke Hoban and I created the equivalent in C# to help with some programming. I've included it here because I'm going to be using it fairly often in the code i write.

 
class Structural {
    public override string ToString() {
        return Structural.ToString(this);
    }

    public override bool Equals(object o) {
        return Structural.Equals(this, o);
    }

    public static string ToString(object o) {
        FieldInfo[] fields = o.GetType().GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instancce);

        StringBuilder buffer = new StringBuilder();
        buffer.Append(o.GetType().Name);

        if (fields.Length > 0) {
            buffer.Append('(');

            for (int i = 0; i < fields.Length; i++) {
                FieldInfo field = fields[i];
                buffer.Append(field.GetValue(o));

                if (i < fields.Length - 1) {
                    buffer.Append(", ");
                }
            }

            buffer.Append(')');
        }

        return buffer.ToString();
    }

    public static bool Equals(object o1, object o2) {
        if ( ! o1.GetType().Equals(o2.GetType())) {
            return false;
        }
        
        FieldInfo[] fields = o1.GetType().GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance);
            
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
                
            object fieldVal1 = field.GetValue(o1);
            object fieldVal2 = field.GetValue(o2);
                
            if ( ! object.Equals(fieldVal1, fieldVal2)) {
                return false;
            }
        }
            
        return true;
    }
}

You can either subclass Structural to get this for free, or if you don't want that limitation you can just have your Equals and ToString call the appropriate static members on Structural. Note: if you use this class you should also implement a similar GetHashCode so that your object will be a well behaving citizen.