Generic Delegate Types explained (example)


R

raylopez99

Here is a good example that shows generic delegate types. Read this
through and you'll have an excellent understanding of how to use these
types. You might say that the combination of the generic delegate
type expression in just the right place and a well-named method means
we can almost read the code out loud and understand it without even
thinking.

Note that since 'boxing' and 'unboxing' is involved (I think), you
don't get what you expect if you pass a value type (see the output
labeled "//not what you want - baad!")

To get around this problem, you have to create a delegate parameter
that accepts an object (reference type). Then it works.

However, just to show how 'ref' works, when the generic method called
passes by reference rather than by object (required whenever 'new' is
used in a method) the delegate had to be renamed (from Transformer to
'Uransformer'). Don't know why this is the case but that's the way it
is.

Finally, note how you can change an object whether your pass by value
or by reference, if you are passing the object. This holds even for
strings. Note the strings are changed in both versions of the generic
delegates, "Transform2" and "Transform3".

What to make of 'generic delegate types'? After going through this
example carefully (which was a modification of a extremely primitive
example of generic delegate types taken from p. 108 of the C# 3.0
Nutshell, which formed the first output, involving squaring a value
type), I am convinced generic delegate types are a receipe for
disaster, since essentially they do away with strong typing (whatever
that means, but I have an understanding in my mind's eye).

The only advantage I can see is just a slick way of declaring a
delegate " public delegate T Transformer<T> (T arg); " that will then
allow you to write methods/functions that obey this template of taking
a type T and returning a type T (whether the T is an int, a double, or
even an object), though the methods/functions are radically
different. These methods can then be called using the convention
below. Big deal. A receipe for disaster.

Big deal and a receipe for disaster is not unlike "lambda expressions"
and "anonymous delegates" (both topics I'm not that familiar with).
If anybody can relate how generic delegate types correlate with these
two topics it would be appreciated.

RL

(c) 2008, all rights reserved, by 'artistic license' and void where
prohibited. For skolarly use only. The moral rights of the author are
preserved, whatever that means. Copyleft and free to use without
attribution.

// output

1X! 2X! 3X! 1Sq! 4Sq! 9Sq! 1Cb! 64Cb! 729Cb! //note this works as
expected, since 9x9x9 = 729, and each array element is being changed
as expected.

value now [X]: 2 //not what you want - baad!
value now [Square]: 2 //not what you want - baad!
value now [Cube]: 2 //not what you want - baad!
____________

value now [X]: 3 //not what you want - baad!
value now [Square]: 3 //not what you want - baad!
value now [Cube]: 3 //not what you want - baad!
_____UtilSquarer______

value now [UtilSquarer]: 9 //now works fine
value of string [UtilSquarer]: goodbye!
_____UtilStringChanger______

value of string [UtilStringChanger]: BAA_is_a_goodBuy! //string
changed properly (1 of 2 ways to change string; see below for ref
version)
__UtilStringChanger2AndMoreStaticVer__

values of string, int are [UtilStringChanger2AndMoreStaticVersion]:
anotherStrin
gHere! , 10001
Press any key to continue . . .
/////////////////////

using System;
using System.Collections.Generic;
using System.Text;

namespace p108DelegatesCSNutshell
{
public delegate T Transformer<T> (T arg);
public delegate T Uransformer<T> (ref T arg); //slight name change
needed for 'ref' version, otherwise won't compile


class Program
{
static void Main(string[] args)
{
int[] values = new int[] { 1, 2, 3 };
int value = 2;
Util myUtil = new Util();

Util.Transform1(values, ReturnX); //dynamically hook
ReturnX
foreach (int i in values)
{
Console.Write(i + "X! ");
}

Util.Transform1(values, Square); //dynamically hook square
foreach (int i in values)
{
Console.Write(i + "Sq! ");
}

Util.Transform1(values, myUtil.Cube); //dynamically hook
cube
foreach (int i in values)
{
Console.Write(i + "Cb! ");
}

Console.WriteLine("\n");
myUtil.Transform2(value, ReturnX);
Console.WriteLine("value now [X]: {0}", value);
myUtil.Transform2(value, Square);
Console.WriteLine("value now [Square]: {0}", value);
myUtil.Transform2(value, myUtil.Cube);
Console.WriteLine("value now [Cube]: {0}", value);

Console.WriteLine("____________ \n");

myUtil.Transform2(myUtil.j, ReturnX);
Console.WriteLine("value now [X]: {0}", myUtil.j);
myUtil.Transform2(myUtil.j, Square);
Console.WriteLine("value now [Square]: {0}", myUtil.j);
myUtil.Transform2(value, myUtil.Cube);
Console.WriteLine("value now [Cube]: {0}", myUtil.j);

Console.WriteLine("_____UtilSquarer______ \n");

myUtil.Transform2(myUtil, myUtil.UtilSquarer);
Console.WriteLine("value now [UtilSquarer]: {0}",
myUtil.j);
Console.WriteLine("value of string [UtilSquarer]: {0}",
myUtil.s);

Console.WriteLine("_____UtilStringChanger______ \n");

myUtil.Transform3(ref myUtil, myUtil.UtilStringChanger);
Console.WriteLine("value of string [UtilStringChanger]:
{0}", myUtil.s);

Console.WriteLine("__UtilStringChanger2AndMoreStaticVer__
\n");
myUtil.Transform3(ref myUtil,
UtilStringChanger2AndMoreStaticVersion);
Console.WriteLine("values of string, int are
[UtilStringChanger2AndMoreStaticVersion]: {0} , {1}", myUtil.s,
myUtil.j);


}

static int ReturnX(int x)
{
return x;
}

static int Square(int x)
{
return x * x;
}

static Util UtilStringChanger2AndMoreStaticVersion(ref Util U)
{
U = new Util();
U.s = "anotherStringHere!";
U.j = 10001;
return U;
}

}
}
/////////////////////

using System;
using System.Collections.Generic;
using System.Text;

namespace p108DelegatesCSNutshell
{
class Util
{
public int j;
public string s;
public Util ()
{
s = "hi";
j = 3;
}

public static void Transform1<T>(T[] values, Transformer<T> t)
{
for (int i = 0; i < values.Length; i++)
values = t(values);
}
public int Cube(int x) { return x * x * x; }

public Util UtilSquarer(Util U)
{
U.j = U.j * U.j;
U.s = "goodbye!";

//U = new Util(); //next 3 lines won't work here since ref
not passed
//U.j = U.j * U.j;
//U.s = "NoGo";

return U;

}

public Util UtilStringChanger(ref Util U)
{
U = new Util();
U.s = "BAA_is_a_goodBuy!";
return U;
}


public void Transform2<T>(T value, Transformer<T> t)
{ value = t(value); }

public void Transform3<T>(ref T value, Uransformer<T> t)
{
value = t(ref value);
}

}
}
 
Ad

Advertisements

B

Barry Kelly

raylopez99 said:
I am convinced generic delegate types are a receipe for
disaster, since essentially they do away with strong typing (whatever
that means, but I have an understanding in my mind's eye).

I assert that this post indicates that you have no idea what "strong
typing" means. If you do, in fact, know what strong typing means, you
should be able to convince me by showing me the type violation.

Go on. Show me.

-- Barry
 
M

Marc Gravell

Seiously, this just isn't the right forum for this type of post. Try
something like codeproject or a blog. But to disect it:

Not only is your post full of errors both of fact and omission (and
unclear at best), you repeatedly remark that you don't understand
certain things, but somehow feel qualified to comment on them at the
same time. This post simply isn't helpful to anyone - although no
doubt another long but ultimately fruitless chain will follow. Come
on, please stop with the trolling / flamebaiting so we can actually
keep this forum useful.

Marc
 
J

Jeroen Mostert

raylopez99 said:
Here is a good example that shows generic delegate types.

Only half of that sentence is correct.

Don't draw conclusions if you already know you don't have all the facts. Ask
questions instead. Conclusions don't teach. Attempting to explain things to
an audience may help raise questions, but leave it at that.
 
R

raylopez99

Only half of that sentence is correct.

Don't draw conclusions if you already know you don't have all the facts. Ask
questions instead. Conclusions don't teach. Attempting to explain things to
an audience may help raise questions, but leave it at that.

What is your question? Or do you just like making accusations?

RL
 
R

raylopez99

I assert that this post indicates that you have no idea what "strong
typing" means. If you do, in fact, know what strong typing means, you
should be able to convince me by showing me the type violation.

Go on. Show me.

-- Barry

--http://barrkel.blogspot.com/

Wikipedia claims there's no generally agreed definition of 'strong
typing', last I checked.

So it was a trick question.

BTW, for the rest of you too lazy to read my code--one interesting
thing I found was this:

public void Transform2<T>(T value, Transformer<T> t)
{ value = t(value); }

if "T" is a primitive data type (non-object, like int), the generic
delegate Transform2 will not work. But if it's an object, it will, as
I demonstrated in my OP.

Beats me why.

RL
 
Ad

Advertisements

J

Jon Skeet [C# MVP]

raylopez99 said:
Wikipedia claims there's no generally agreed definition of 'strong
typing', last I checked.

Certainly, but to claim that generic delegates do away with strong
typing is simply wrong by *any* widely used definition of strong
typing.
So it was a trick question.

Not really. While there are various understandings, none of them are
compatible with your claim that generic delegates "do away with strong
typing". Once again your "mind's eye" seems to be highly flawed. I'd
stop trusting it, if I were you...
BTW, for the rest of you too lazy to read my code--one interesting
thing I found was this:

public void Transform2<T>(T value, Transformer<T> t)
{ value = t(value); }

if "T" is a primitive data type (non-object, like int), the generic
delegate Transform2 will not work. But if it's an object, it will, as
I demonstrated in my OP.

Um, it will work perfectly well actually.

Admittedly changing the value of a parameter (effectively a local
variable) and then ignoring the new value is somewhat odd, but it will
still operate perfectly well.

Of course, it depends on what you expect it to do. If you could post a
short but complete program (one which only tries to demonstrate *one*
thing) which doesn't work as you expect it to, we can explain your
misunderstanding.
 
G

Göran Andersson

raylopez99 said:
BTW, for the rest of you too lazy to read my code--one interesting
thing I found was this:

public void Transform2<T>(T value, Transformer<T> t)
{ value = t(value); }

if "T" is a primitive data type (non-object, like int), the generic
delegate Transform2 will not work. But if it's an object, it will, as
I demonstrated in my OP.

Beats me why.

RL

No, it doesn't work properly either with value types or reference types.
As I explained in my other post, assigning the return value from the
delegate call to the value argument has no effect at all, as the
argument value is a copy and will never be used any more.

If the generic type is a reference type, the delegate method can change
the members of the object, but it can not change the object itself.
Also, if the class is immutable like for example the String class, the
delegate method can't change any members of the object, so it will not
be able to change anything at all.
 
B

Barry Kelly

Wikipedia claims there's no generally agreed definition of 'strong
typing', last I checked.

Assuming Wikipedia had a monopoly on the truth, then how can you claim a
type violation, if such a thing is undefined?

Or on the other hand, perhaps there is some set of definitions of
"strong typing" - which I disagree with, BTW, I think the term is well
defined for any given type system, and with the CLR we do indeed have a
type system - but even if so, you should be able to choose *one* of
them, and show the type violation.

I'm still waiting.

-- Barry
 
R

raylopez99

Um, it will work perfectly well actually.

Admittedly changing the value of a parameter (effectively a local
variable) and then ignoring the new value is somewhat odd, but it will
still operate perfectly well.

Of course, it depends on what you expect it to do. If you could post a
short but complete program (one which only tries to demonstrate *one*
thing) which doesn't work as you expect it to, we can explain your
misunderstanding.

I did post complete code. COpy and paste it. I omitted nothing.

Why didn't squaring the number work in these code fragments?

//
value = t(value);

public int Cube(int x) { return x * x * x; }

myUtil.Transform2(value, myUtil.Cube);
Console.WriteLine("value now [Cube]: {0}", value);
//

Only when I changed it to this (an object) did it work properly:

//
public Util UtilSquarer(Util U)

{
U.j = U.j * U.j;
//

RL
 
Ad

Advertisements

R

raylopez99

No, it doesn't work properly either with value types or reference types.
As I explained in my other post, assigning the return value from the
delegate call to the value argument has no effect at all, as the
argument value is a copy and will never be used any more.

Then show us how it can work other than my solution, which I
demonstrated. Quite playing semantic games like Jon likes to do. It
doesn't make you, a programmer, look more scientific in front of the
hard sciences like engineering and physics. It just makes you look
like a trivial pedant.


RL
 
M

Marc Gravell

Why didn't squaring the number work in these code fragments?

Because Transform2 is defined as:

public void Transform2<T>(T value, Transformer<T> t)
{ value = t(value); }

And you never retreive the "value" arg. The most sensible fix would be
to have Transform2 "return value;" at the end; then I suspect you'll
find that:

value = myUtil.Transform2(value, myUtil.Cube);

will update value. Or treat value as a ref argument.

Marc
 
M

Marc Gravell

I have answered this (with example) on your other e-mail. But these are
not semantic games nor is it pedantry - they are essential terms that
you have clearly not understood fully, but which you *need* to learn if
you are going to understand what is going on here.

Marc
 
J

Jon Skeet [C# MVP]

I did post complete code. COpy and paste it.  I omitted nothing.

But you were trying to show too much in one program. That's why I
asked you for a short but complete program which only tries to
demonstrate *one* thing. That way it's a lot easier to talk about.
Why didn't squaring the number work in these code fragments?

Because you *still* appear not to understand parameter passing.
See http://pobox.com/~skeet/csharp/complete.html

Hint: this has nothing to do with delegates. Do the same thing with
normal method calls and you'll see the same result.

Jon
 
Ad

Advertisements

G

Göran Andersson

raylopez99 said:
Then show us how it can work other than my solution, which I
demonstrated.

I did. Read my reply to your OP, where I have pointed out in detail most
of the mistakes in your code.
 
R

raylopez99

Because Transform2 is defined as:

         public void Transform2<T>(T value, Transformer<T> t)
         { value = t(value); }

And you never retreive the "value" arg. The most sensible fix would be
to have Transform2 "return value;" at the end; then I suspect you'll
find that:

   value = myUtil.Transform2(value, myUtil.Cube);

will update value. Or treat value as a ref argument.

Marc

Thanks Marc--your suggestion worked. Just made the changes now, and
it's interesting. The ref argument 'fix' I've already done in my
original post; this below code is using a return value.

There are two keys to making this example work with non-classes (ints,
doubles, etc): you need to fix the 'transform' (generic delegate
template or whatever) to have a return type when dealing with value
types, and, importantly, you need to assign the return to a Left Hand
Side.

RL

int NewInt = 69;

myUtil.Transform2Marc(NewInt, Square);

Console.WriteLine("value now [69 squared? no!]: {0}", NewInt);

//note, you must assign the RHS to a LHS for this to work, hence the
penultimate line above fails.
//no! 69 not squared to 69^2! does not work

int NewIntAnotherVariable; // key! to have a LHS (left hand side)

NewIntAnotherVariable = myUtil.Transform2Marc(NewInt, Square); //try
this, different? Yes!

Console.WriteLine("value now [69 squared? Yes!]: {0}",
NewIntAnotherVariable);

//works! gives 4761, which is 69*69.


///////// THis is the fix, adding one line as suggested by Marc

public T Transform2Marc<T>(T value, Transformer<T> t)
{
value = t(value);
return value; //ADD THIS LINE: important change allows
'primitive' data types to be assigned values (compare to objects)
}

///as an aside, and nothing to do with this post, this below code does
not work as generic--I believe because C# does not support, like in C+
+ making generics of functions, but only making generics of class
types, but I'm not sure

// static T cuber (<T>)
//{
// return T*T*T*T;

//}

// cuber fails to compile. You need to preface T with 'int' or
'double' or a class name for cuber to compile.

What an excellent learning exercise this is/was for anybody reading
this thread. This is the best of Usenet.

RL
 
G

Göran Andersson

raylopez99 said:
Thanks Marc--your suggestion worked. Just made the changes now, and
it's interesting. The ref argument 'fix' I've already done in my
original post; this below code is using a return value.

There are two keys to making this example work with non-classes (ints,
doubles, etc): you need to fix the 'transform' (generic delegate
template or whatever) to have a return type when dealing with value
types, and, importantly, you need to assign the return to a Left Hand
Side.

RL

Was that really what you were looking for? Why didn't you ask for that
in the first place, instead of writing a highly confused article trying
to explain something that you don't understand?

I've seen many different ways in how people fail to ask a proper
question, but this has to be the strangest by far...
 
Ad

Advertisements

R

raylopez99

Was that really what you were looking for? Why didn't you ask for that
in the first place, instead of writing a highly confused article trying
to explain something that you don't understand?

Because it was not apparent until after the fact.

RL
 

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