C# and generic code

Started by
21 comments, last by bytecoder 17 years, 9 months ago
Ive been programming in D for a year or so now, but the lack of developer environment and support(code) made me look for something similar without those problems, hence: C#. I really liked what i saw on first impression, and picked a project to start testing it out. On of the first things i needed was a mathematical vector class. Obviously it would be nice to create one of unspecified type. This appeared to be impossible. After some internet searches, it still seems to be impossible without ugly hacks that affect runtime performance. Please tell me this is a joke. This language seems to have so much potential, how can they seriously consider implementing rules (of which i have yet to see the point) that disallow you to do something so basic? If they really insist on that, why dont all language native arithmetic types implement some sort of 'Arithmetic' interface? Not being able to perform operators on language native templated types seems simply unacceptable to me.
Advertisement
To end this with a clear question: this cant be true, can it?

If it is, where then can i find a powerfull, modern and well-supported language?
Hrm, it does at first glance appear to be correct.

The basic problem (for those that don't have time to hunt for links) is that C# generic types have no guarantee to have operators defined. Thus T op T is undefined. C# allows type constraints, but that requires the specified type to inherit from an abstract base class (understandably unacceptable), or from an interface with the operators defined. oops operator overloading in C# is done with static functions, and interfaces can't implement static functions.

[Caveat: I am no great guru, and only have a vague understanding of language and compiler internals. The following 2 paragraphs is somewhat supposition]

So, T op T is left undefined. In C++/D this doesn't matter much. The type is replaced in T, and if T happens to have op; great. Otherwise you'll get a compiler error dumped out. In C# this is pretty important; C# doesn't plop in the type. C# compiles to a generic object, and upon instantiation JIT's it into the resolved type. So that's the rule choice. So what should happen if the generic type gets the actual type while running and it doesn't support the operation? Keel over and die?

And that ignores reflection, serialization, and other goodies that C#/.NET provides on types (even generic types) that would likely be quite a bit more difficult, if not impossible to provide with full flavored templates.


Give it some time and practice. Generics/templates are weak in C# (and everything else) compared to C++/D. Other features (everything deriving from Object, the Type object, attributes) alleviate the need for templates/generics quite a bit. I know it took me a while to realise that and learn to code in C# style rather than C# in C++ style.

[edit: Linky]
O'Caml is a very nice, modern and powerful language. Whether or not you'd consider it well supported, I can't say. I'm more interested in the languages themselves than their libraries.

But I'll have to admit: O'Caml suffers from the same problem as C#: you won't be able to make one vector-"class" that would be perfectly parameterizable, working with integers and on floats (I already welcome jdh30 to this thread, ready to contradict this).
yeah, this was one of the big things that dissapointed me about C# Generics. I mean, I still use them and they're great ... but support for this kind of thing would have been awesome.

Now, just for the sake of discussion, I'm assuming by your little performance/hack comment that you read Eric Gunnerson's take on the issue? He too mentioned the performance issues. Unfortunately, I don't think he ever went back and tested this, but one of the comenters suggested a way to get around the performance issues brought on by the virtual method calls by using interfaces.

If by chance you do get it to work, I would be quite interested in seeing your implementation as I've always wanted to do just what you're suggesting :-)
Joel Martinez
http://codecube.net
[twitter]joelmartinez[/twitter]
this piece from your linked interview seems to sum it up nicely:
Quote:
Now, in C#, operators are static members. So, an operator can never be a member of an interface, and therefore an interface constraint could never endow you with an operator+. The only way you can endow yourself with an operator+ is by having a class constraint that says you must inherit from, say, Number, and Number has an operator+ of two Numbers. But you could not in the abstract say, "Must have an operator+," and then we polymorphically resolve what that means.

Bill Venners: You did the constraints by type, not by signature.

Anders Hejlsberg: Yes.

Bill Venners: So the type must either extend a class or implement interfaces.

Anders Hejlsberg: Yes. And we could have gone further. We did give thought to going further, but it gets very complicated. And it's not clear that the added complexity is worth the small yield that you get. If something you want to do is not directly supported in the constraint system, you can do it with a factory pattern. You could have a Matrix<T>, for example, and in that Matrix you would like to define a dot product method. That of course that means you ultimately need to understand how to multiply two Ts, but you can't say that as a constraint, at least not if T is int, double, or float. But what you could do is have your Matrix take as an argument a Calculator<T>, and in Calculator<T>, have a method called multiply. You go implement that and you pass it to the Matrix.

Bruce Eckel: And Calculator is a parameterized type also.

Anders Hejlsberg: Yes. It is sort of a factory pattern. So there are ways of doing these things. It's maybe not quite as nice as you'd like, but everything comes at a price.

but forgive my noobness: i dont quite understand this 'factory pattern' solution. does it have serious runtime performance implications? if so, i dont even want to bother knowing more about it, but if its something i can stuff away in my vector source file never to be bother again with, im willing to put up with it :).
Perhaps something like this?

namespace VecTest{    class Program    {        static void Main(string[] args)        {            Vector<int> u = Vector<int>.Create( 5, 8 );            Vector<int> v = Vector<int>.Create( 3, 1 );            Vector<int> s = u + v;            Console.WriteLine( "{0} + {1} = {2}", u, v, s );        }    }    abstract class Calculator<T>    {        public abstract T Add(T x, T y);        public abstract T Mul(T x, T y);    }    class IntCalc : Calculator<int>    {        public override int Add(int x, int y)        {            return x + y;        }        public override int Mul(int x, int y)        {            return x * y;        }    }    class FloatCalc : Calculator<float>    {        public override float Add(float x, float y)        {            return x + y;        }        public override float Mul(float x, float y)        {            return x * y;        }    }    struct Vector<T>    {        public static Vector<int> Create(int x, int y)        {            return new Vector<int>(new IntCalc(), x, y);        }        public static Vector<float> Create(float x, float y)        {            return new Vector<float>(new FloatCalc(), x, y);        }        private Vector(Calculator<T> calc, T x, T y)        {            _calc = calc;            X = x;            Y = y;        }        public static Vector<T> operator +(Vector<T> u, Vector<T> v)        {            Calculator<T> c = u._calc;            T sx = c.Add( u.X, v.X );            T sy = c.Add( u.Y, v.Y );            return new Vector<T>( c, sx, sy );        }        public override string ToString()        {            return string.Format( "({0}, {1})", X, Y );        }        private Calculator<T> _calc;        public T X;        public T Y;    }}


Maybe turning Calculator into a struct might improve performance. Also, is there no way of defining a member of the Vector "metaclass", not Vector<T>? The static factory methods Create(...) do not need T to be specified.
Quote:Original post by SamLowry
O'Caml is a very nice, modern and powerful language. Whether or not you'd consider it well supported, I can't say. I'm more interested in the languages themselves than their libraries.

But I'll have to admit: O'Caml suffers from the same problem as C#: you won't be able to make one vector-"class" that would be perfectly parameterizable, working with integers and on floats (I already welcome jdh30 to this thread, ready to contradict this).

Actually, you can, you just have to use variants.
type number = Int of int | Float of float

Of course, this requires wrapping all your constants with the correct type constructor and it's not very fast--one of the reasons I dislike OCaml.

edit:
Oh, and did I mention that you'd also either have to 1) redefine the built-in operators (+ - * /) to accept either ints or floats, or pattern match everywhere in your code. The former would look something like this:
let (+|) x y = match (x, y) with        Int x, Int y -> Int (x + y)        | Int x, Float y -> Float ((float_of_int x) +. y)        | Float x, Int y -> Float (x +. (float_of_int y))        | Float x, Float y -> Float (x +. y);;...

And would have to be used like:
Float 10.0 +| Int 10


Did I mention I don't like OCaml?
Quote:Original post by bytecoder
Quote:Original post by SamLowry
O'Caml is a very nice, modern and powerful language. Whether or not you'd consider it well supported, I can't say. I'm more interested in the languages themselves than their libraries.

But I'll have to admit: O'Caml suffers from the same problem as C#: you won't be able to make one vector-"class" that would be perfectly parameterizable, working with integers and on floats (I already welcome jdh30 to this thread, ready to contradict this).

Actually, you can, you just have to use variants.
type number = Int of int | Float of float

Of course, this requires wrapping all your constants with the type constructors and it's not very fast--one of the reasons I dislike OCaml.


I knew that someone would give me that solution! Ordinarily it's jdh30 :)
I certainly do miss Haskell's type classes in O'Caml.
Quote:Original post by SamLowry
Quote:Original post by bytecoder
Quote:Original post by SamLowry
O'Caml is a very nice, modern and powerful language. Whether or not you'd consider it well supported, I can't say. I'm more interested in the languages themselves than their libraries.

But I'll have to admit: O'Caml suffers from the same problem as C#: you won't be able to make one vector-"class" that would be perfectly parameterizable, working with integers and on floats (I already welcome jdh30 to this thread, ready to contradict this).

Actually, you can, you just have to use variants.
type number = Int of int | Float of float

Of course, this requires wrapping all your constants with the type constructors and it's not very fast--one of the reasons I dislike OCaml.


I knew that someone would give me that solution! Ordinarily it's jdh30 :)
I certainly do miss Haskell's type classes in O'Caml.

*ahem* :) Yeah, I tend to like haskell more than OCaml, too.

edit:
I realize now that that's not particularly relevant :/ Oh well, at least it's interesting.

This topic is closed to new replies.

Advertisement