Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using constrained generics instead of interfaces -- downsides?

Let's say I have

interface IMatrix {
    double this[int r, int c] { get; }
}

struct Matrix2x2 : IMatrix  {
    double a1, a2, b1, b2;
    double this[int r, int c] { get { ... } }
}

struct Matrix3x3 : IMatrix {
    double a1, a2, a3, b1, b2, b3, c1, c2, c3;
    double this[int r, int c] { get { ... } }
}

class Matrix : IMatrix {    // Any size
    double[,] cells;
    double this[int r, int c] { get { ... } }
}

Sometimes, instead of just saying

static class Matrices {
    static IMatrix Multiply(IMatrix a, IMatrix b) { ... }
}

I end up doing

static class Matrices {
    static IMatrix Multiply<T1, T2>(T1 a, T2 b)
        where T1 : IMatrix
        where T2 : IMatrix { ... }
}

or maybe even

static class Matrices {
    static IMatrix Multiply<T1, T2>([In] ref T1 a, [In] ref T2 b)
        where T1 : IMatrix
        where T2 : IMatrix { ... }
}

to avoid boxing or copying structs.

It works fine and everything, but are there any downsides I don't know about (other than a negligible increase in memory usage)? Is this an accepted practice, or is it discouraged for any reason I might not be aware of?

like image 851
user541686 Avatar asked Dec 03 '11 20:12

user541686


2 Answers

Generics come with a small cost, mostly around larger code size. A recent blog post from Joe Duffy gives a fairly detailed look at this. However, in general, avoiding boxing for frequently called code is a good thing and probably worth more generated byte code (in practice, this means slightly higher memory usage and more work for the JIT).

like image 88
bobbymcr Avatar answered Oct 25 '22 09:10

bobbymcr


Interface constraints are wonderful, but there is an important caveat: while it's possible for structures to have either immutable semantics, mutable value semantics, or mutable reference semantics(), boxed structures, if mutable, always have reference semantics. While mutable value semantics are often useful(*), it can be difficult to make them work nicely with generics. Consider even a question like whether the parameters to the matrix multiply you list should be passed by reference or by value: if the generic type is a value type, the parameters should probably be passed by reference; if it's a class type, they should be passed by value.

As another example of the reference/value distinction, suppose a method has a matrix, and what it wants to have is a matrix which is just like the old one except with elements (0,0) and (1,1) zeroed, and it won't need the original matrix anymore. If the matrix is a mutable value type with a settable indexed property, the method can simply write to elements (0,0) and (1,1) without unwanted side-effects; if it's a mutable reference type the routine would likely have to make a defensive clone first (ick); if it's an immutable type, it may have to create a new instance for each modification (ick). Using a value type can offer much cleaner semantics, but can cause unexpected side-effects if the system does code transformations like boxing which change value-type semantics into reference semantics or broken semantics.

(*) A struct which simply exposes mutable fields exhibits very clean mutable value-type semantics. Given the declarations:

public struct valueStruct {public int value; ... }
public interface IManipulateValueStruct {public void manipulate(ref valueStruct it);}
public void someMethod(IManipulateValueStruct manipulator1, manipulator2)
{
  valueStruct x,y;
  ...
  manipulator1.Manipulate(ref x);
  manipulator2.Manipulate(ref y);  // What does this method do with x and y?
  ...
}

Looking just at the above code, one can determine that indicated method call may affect y.value, but will not affect x.value, nor will it cause y.value to change at any time after the method call has returned. By contrast, if valueStruct were instead a class, there's no telling what the indicated call could do to x.value, nor any way of telling whether it might cause y.value to change at some arbitrary future time.

Although structures with exposed fields implement mutable value semantics, it is possible for structures to implement mutable reference semantics if they hold an immutable field of class type, and if the only mutators they expose act upon that field. This can at times be a useful pattern, though somewhat restricted because the C# and vb compilers will forbid operations that look like they might attempt to mutate the structure (but would really mutate the class object to which it holds a reference). Note that immutable structs with mutable reference semantics must be initialized with a non-default constructor in order to be useful. Such structs that are created via default initialization will generally be broken and useless; at best they will be indistinguishable from each other.

like image 31
supercat Avatar answered Oct 25 '22 09:10

supercat