Question regarding generics and type constraints

F

Fredo

I'm new to Generics (years and years of VS.NET 2003 development, but very
little .NET 2.0+). I have some routines for conversion from RGB to different
color spaces and back. I would like the routines to support, at least, byte,
int, and double types without having to create different versions of the
routines. Is there any way to do this with generics?

The problem is, if I do something like this:

public static void RGB_to_YIQ<T>(T[] R, T[] G, T[] B, T[] Y, T[] I, T[] Q)
{
Y[index] = 0.299 * R[index] + 0.587 * G[index] + 0.114 * B[index];
...
}

I get an error: "Operator '*' cannot be applied to operands of type 'double'
and 'T'"

Like I said, I'm a newb to the whole generics thing. I read the "Constraints
on Type Parameters" stuff using "where" in MSDN, but I'm not sure how I can
use that, if I can.

Thanks.
 
M

Marc Gravell

There is an issue with using operators with generics.

If you have .NET 3.5, I have a full and easy solution to this problem here:

http://www.pobox.com/~skeet/csharp/genericoperators.html
(also briefly explains the problem)

Note the "usage" page, and the download via "MiscUtil" (the two links in the
opening lines).

Basically, you can various things involving Operator.Multiply(x,y),
Operator.Add(x,y) - you might also need Operator.Convert<float,T> (or
something) to handle your literals (0.299 etc).

I'm about to run away for a few hours, but post back if you want more
info...

Marc
 
F

Fredo

Marc,

Interesting stuff. This code is generally used in performance-critical areas
and it looks like your code would add a good bit of overhead converting all
the operators to method calls, though I don't know enough about it to be
sure.

If it's going to impede performance significantly, I'll just stick to
separate versions of the code.
 
M

Marc Gravell

Interesting stuff. This code is generally used in performance-critical areas
and it looks like your code would add a good bit of overhead converting all
the operators to method calls, though I don't know enough about it to be
sure.

It actually inlines pretty well, especially for non-nullable types -
but if this is 100% performance critical, then I'd have separate
Int32, Byte, etc methods; but in all seriousness - the slowdown isn't
as bad as you might expect, since this the expression is compiled once
(only) per type and re-used. You lose a bit of time in delegate
invoke, but thats about it.

Marc
 
M

Marc Gravell

For idle curiosity, I ran some tests; results as follows (first number
is milliseconds for 5M iterations, second number is a checksum to
prove did the same thing):

Double Static: 48 (35.105617)
Double Generic: 95 (35.105617)
Float Static: 40 (35.10562)
Float Generic: 112 (35.10562)
Int Static: 83 (35)
Int Generic: 113 (35)
Byte Static: 85 (35)
Byte Generic: 576 (35)

The Byte is surprising (perhaps I could optimise that a bit), but this
is testing using loops cloned from below.

The main point I'm making here is: don't write it off, even for high
performance loops! The Operator class might not be suitable, but you
can roll your own Expression and compile it (full code shown). A
factor of x2 (double) or even x1.3 (int) may be perfectly acceptable
in many circumstances, especially if it reduces the code. Not sure
what happened with Byte though! ;-p

Marc

static void TestDecimal() {
double r = 15.123, g = 52.1, b = 0.01, y, i, q;
ColorConverter.RgbToYiq(r, g, b, out y, out i, out q);
Stopwatch watch = new Stopwatch();
watch.Start();
for (int j = 0; j < 5000000; j++)
{
ColorConverter.RgbToYiq(r, g, b, out y, out i, out q);
}
watch.Stop();
Console.WriteLine("Double Static: {0} ({1})",
watch.ElapsedMilliseconds, y);
watch.Reset();
ColorConverter<double>.RgbToYiq(r, g, b, out y, out i, out
q);
watch.Start();
for (int j = 0; j < 5000000; j++)
{
ColorConverter<double>.RgbToYiq(r, g, b, out y, out i,
out q);
}
watch.Stop();
Console.WriteLine("Double Generic: {0} ({1})",
watch.ElapsedMilliseconds, y);
}

Where:

static class ColorConverter
{
public static void RgbToYiq(double r, double g, double b,
out double y, out double i, out double q)
{
y = 0.299 * r + 0.587 * g + 0.114 * b;
i = q = 0;
}
...
}

And:

static class ColorConverter<T> {
public static void RgbToYiq(T r, T g, T b, out T y, out T
i, out T q)
{
y = Y(r, g, b);
i = q = default(T);
}
static readonly Func<T, T, T, T> Y;
static ColorConverter()
{
ParameterExpression r =
Expression.Parameter(typeof(T), "r"),
g = Expression.Parameter(typeof(T), "g"),
b = Expression.Parameter(typeof(T), "b");

Y = Expression.Lambda<Func<T, T, T, T>>(
Expression.Convert(
Expression.Add(
Expression.Add(
Expression.Multiply(
Expression.Constant(0.299,
typeof(double)),
Expression.Convert(r,
typeof(double))
),
Expression.Multiply(
Expression.Constant(0.587,
typeof(double)),
Expression.Convert(g,
typeof(double))
)
),
Expression.Multiply(
Expression.Constant(0.114,
typeof(double)),
Expression.Convert(b,
typeof(double))
)
),
typeof(T)), r, g, b).Compile();
}
}
 
M

Marc Gravell

"TestDecimal"

it was just the method name that was wrong; this should have been
TestDouble; results stand "as is" though.

Basically, I started with decimal and then moved to double because
that was in your list; I forgot to rename.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top