?
Solved

Generics - where with comparable - help please

Posted on 2012-09-04
48
Medium Priority
?
666 Views
Last Modified: 2012-09-05
Hi,

I have more than 10 years programming experience, mostly in Java (up until 1.4), and now I got back to my learning of C#, even though I have had a good grasp of the main concepts behind the language and did some programming as well.

That said, I am now using Generics and from the source code below I don't understand the meaning of where and how and why it is used. I am also having had a bad time trying to understand the T thing there. For example, in the case of the parameter list, why not simply send a List as a type instead of T[] list. I understand though the Generics concept that prevents errors of mixing types in compile time.

Any help will be greatly appreciated.

    class Sample
    {
        static void Main()
        {
            int[] array = { 0, 1, 2, 3 };
            MakeAtLeast<int>(array, 2); // Change array to { 2, 2, 2, 3 }
            foreach (int i in array)
                Console.WriteLine(i); // Print results.
            Console.ReadKey(true);
        }


        //T[] list => my own comment: passing by reference
        static void MakeAtLeast<T>(T[] list, T lowest) where T : IComparable<T>
        {
            for (int i = 0; i < list.Length; i++)
                if (list[i].CompareTo(lowest) < 0)
                    list[i] = lowest;
        }
    }

Open in new window

0
Comment
Question by:DistillingExperts
  • 15
  • 10
  • 9
  • +3
48 Comments
 
LVL 13

Assisted Solution

by:Ryan
Ryan earned 80 total points
ID: 38363307
It's just a keyword to require that the type being passed as T must implement, in this case, iComparable which just enforces .CompareTo() to be defined.
0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 38363313
It is a constraint. In this case it means that any type used in the place of T must implement the IComparable interface.
0
 
LVL 45

Assisted Solution

by:AndyAinscow
AndyAinscow earned 80 total points
ID: 38363326
As to the T itself.  That, as you know, is for a generic collection.  Basically it means you can use one function and tell it what sort of parameter it is accepting - in this case int.  You could have an array of doubles, dates... all passed into one function rather than having a lot of duplicate code, one copy for int, one for double, one for date.....


ps.  T doesn't have to be used for a collection, I just said that here because you are passing a collection into the function.
0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
LVL 11

Expert Comment

by:b_levitt
ID: 38363335
See "Why use constraints":
http://msdn.microsoft.com/en-us/library/d5x73970.aspx

In your particular example, your MakeAtLeast method takes two templated parameters "list" and "lowest".  The where constraint means that those two parameters can't simply be of any type but must at least implement IComparable of the same type.

By including this constraint, the compiler knows your line "list[ i ].CompareTo(lowest)" is legal without any casting.  Without generics, you'd need to take to parameters of type object, but in doing so you would have to cast and handle casting errors.
0
 

Author Comment

by:DistillingExperts
ID: 38363376
Thank to all.

kaufmed:
"It is a constraint. In this case it means that any type used in the place of T must implement the IComparable interface."

Why then can I not say only where IComparable<T> instead of where T : IComparable<T>?

b_levitt:
"By including this constraint, the compiler knows your line "list[ i ].CompareTo(lowest)" is legal without any casting.  Without generics, you'd need to take to parameters of type object, but in doing so you would have to cast and handle casting errors."

Would be easy for you to provide the same code without using generics, as you suggested?

AndyAinscow:
"You could have an array of doubles, dates... all passed into one function rather than having a lot of duplicate code, one copy for int, one for double, one for date....."

How it would look like in a code?
0
 
LVL 75

Accepted Solution

by:
käµfm³d   👽 earned 1200 total points
ID: 38363389
Why then can I not say only where IComparable<T> instead of where T : IComparable<T>?
Because the syntax is defined that way! Aside from that, you can actually have a generic method with multiple type parameters:

e.g.

DoSometthing<T, S>(T input1, S input2)
    where T : struct,
          S : class

Open in new window


...and you may need a constraint on each type.
0
 

Author Comment

by:DistillingExperts
ID: 38363412
I understand the syntax factor, but still you only said about the IComparable<T>, so why not say only where IComparable<T>, since both Ts seems to be the same ever? Syntax again?

.. Sorry, I think this answers my question:

"...and you may need a constraint on each type."
0
 

Author Comment

by:DistillingExperts
ID: 38363506
I still have another issue with this code. If I do this:

int[] array = { 0, 1, 2, 3, '3' };

Open in new window


The compiler won't complain, I will get a wrong outcome and, thus, somehow I broke the Generics concept of preventing developers to make mistakes with type. Is that correct?
0
 
LVL 11

Assisted Solution

by:b_levitt
b_levitt earned 400 total points
ID: 38363612
As requested, the code required without the constraint:

try
{
  if ( ((IComparable<T>)list[i]).CompareTo((IComparable<T>)lowest) < 0 )
    ....
}
catch(InvalidCastException)
{
  throw new ApplicationException(typeof(T).Name + " does not implement IComparable.  Consequently, 'lower' cannot be compared to the elements of 'list' in order to determine if a replacement is necessary.")
}

Open in new window

0
 
LVL 11

Expert Comment

by:b_levitt
ID: 38363623
In your array example, there is an inherent cast of your '3' to integer 51 (ascii).
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 38363640
int[] array = { 0, 1, 2, 3, '3' };
                                           
The '3' is the character 3, not the number 3.
I've just had a quick compile - you are correct there is no warning - not so good, a nasty little bug possibility there.
0
 
LVL 11

Expert Comment

by:b_levitt
ID: 38363671
I understand that, that's why the cast is to 51 and not 3.  Char is an integral data type:
http://msdn.microsoft.com/en-us/library/exx3b86w(v=vs.100).aspx

"A char can be implicitly converted to ushort, int, uint, long, ulong, float, double, or decimal"
http://msdn.microsoft.com/en-us/library/x9h8tsay(v=vs.100).aspx

If you replaced the '3' with "3", I'd bet you get a cast error.
0
 
LVL 17

Expert Comment

by:krakatoa
ID: 38363680
No, int and char are interchangeable. It's not a bug.
0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 38363715
In your array example, there is an inherent cast of your '3' to integer 51 (ascii).
Small correction:

"In your array example, there is an implicit cast of your '3' to integer 51 (ascii)."
0
 
LVL 17

Expert Comment

by:krakatoa
ID: 38363768
int[] i = new int[255];
		
		for(int j=0;j<255;j++){i[j]=++j;} 
		char[] c = new char[255];
		for(int k=0;k<255;k++){c[k]=(char)i[k++];}

for(int y=0;y<i.length;y++){System.out.println("int[] value is "+i[y]+" char[] value is "+(int)c[y]+" the char being expressed as int.");}
for(int y=0;y<i.length;y++){System.out.println("int[] value is "+(char)i[y]+" char[] value is "+c[y]+" the int being expressed as char.");}

Open in new window

0
 
LVL 13

Expert Comment

by:Ryan
ID: 38363769
When I code in C#/VB, I prefer to use Option Strict to avoid mistakes like that.  It limits some of the power of C, but I find I waste more time with obscure errors like accidental implicit casting that I didn't intend.

I don't often code in c++ to know how its handled. A quick google is giving me different methods.
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 38363783
@MrBullwinkle - this is C# not C or C++
0
 
LVL 17

Expert Comment

by:krakatoa
ID: 38363849
Well; only need this, to illustrate the point really:

int[] i = new int[(int)((char)254)];

Open in new window

0
 

Author Comment

by:DistillingExperts
ID: 38363944
Nice discussion here. My main point is around what MrBullwinkle wrote, which he did something to "avoid mistakes like that". It's exactly what I meant. This '3' I added should be viewed as a mistake and I understand what the language does, but why all the hype behind Generics if this is still possible?

Is this really a bug like AndyAinsow mentioned (hope he is saying the same bug as I understand it, on the C# language specification)?

I totally understand it should be flexible as well, but what if I want to prevent this flexibility, like Generics does with other data types?
0
 

Author Comment

by:DistillingExperts
ID: 38363964
b_levitt, thank you for the code. That made me feel compelling to use Generics all the time.
0
 
LVL 17

Expert Comment

by:krakatoa
ID: 38363988
I totally understand it should be flexible as well, but what if I want to prevent this flexibility, like Generics does with other data types?

Then you wouldn't be able to do much.

Generics aren't there to legislate for base types.
0
 

Author Comment

by:DistillingExperts
ID: 38364227
Then you wouldn't be able to do much.

Generics aren't there to legislate for base types.

So that's it? If my system needs to do a $ 3 million banking transfer and by mistake it sent '3', which it would rather transfer $ 51 million, what would be the workaround to prevent that, in the source code, aside from tests?
0
 
LVL 17

Expert Comment

by:krakatoa
ID: 38364326
You don't need workarounds - you need Generics.  Where you require int, you use Integer, so you can type-check it. ;)

(PS  - in my previous : for 'base type', read 'primitive' of course.)
0
 
LVL 11

Expert Comment

by:b_levitt
ID: 38364539
Again I think the implicit cast is a separate and very specific issue.  Generics simply give you classes customized to a specific type without duplicating code.  They are not a replacement for business rules and validation.  Plus i can't think of a way in any system where the character issue would be a concern.  The character type is a concept of the compiler and is only going to exist in your code.  Any interface that takes '3' as an input is actually taking the string "'3'"
0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 38364725
@DistillingExperts
If my system needs to do a $ 3 million banking transfer and by mistake it sent '3', which it would rather transfer $ 51 million, what would be the workaround to prevent that, in the source code, aside from tests?
That is a concern of the caller, I think. As far as I know, there is no way to prevent the implicit conversion in this case. You might mitigate it by creating an overload of your function that takes in an int parameter in one case, a char parameter in another, etc., etc.

@b_levitt
Any interface that takes '3' as an input is actually taking the string "'3'"
I do not agree with that assertion.
0
 

Author Comment

by:DistillingExperts
ID: 38364903
I am not sure if my scenario is the most appropriate one, but elaborating further, let's say the problem occurred and I or somebody else didn't use Integer instead of int, maybe because he or she came from a non-object-oriented language and is simply not used to.

The outcome would be a huge bug and bad image to the company, to say the least. My boss would come to me as I am the one accountable for the system and I would say to him I did everything right, but there was no way to prevent the C# language to do that. I would lose my job for sure.

Anyway, @b_levitt
Any interface that takes '3' as an input is actually taking the string "'3'"

Take my first code with the added '3' and try to do some calculations. You will see the above statement is not true.
0
 
LVL 11

Expert Comment

by:b_levitt
ID: 38364969
@b_levitt
Any interface that takes '3' as an input is actually taking the string "'3'"

I do not agree with that assertion.
@kaufmed
That's fine but please provide evidence to the contrary.  In the context of C#, I'm unaware of how any external system could pass a string of "'3'" and have it accidentally be converted to a char of value '3' without explicitly coding it to do so.
0
 
LVL 11

Expert Comment

by:b_levitt
ID: 38365073
Take my first code with the added '3' and try to do some calculations. You will see the above statement is not true.

I don't disagree that there is a potential for a programmer to mess this up.  Of course, in the days of color coding, you'd be hard pressed to not call that a pretty dumb bug and it's a dumb bug in compiled code.

But your 3 million example wasn't about compiled code - it was an input from an external system and I'm saying there are very few ways that you can feed a system '3' and have it be interpreted as char '3'/integer 65.

Take a simple Add(int a, int b) method and try to pass in '3' for parameter b from some interface - web form, windows form, web service, it doesn't really matter.  Without code to specifically parse the single quotes, there are hardly any ways to mess this up without getting an exception.
0
 
LVL 11

Expert Comment

by:b_levitt
ID: 38365137
At this point you may want to start a new question debating the merits of the implicit cast of char.  This is standard in other languages as well.  In the end the designers thought that it was reasonable to cast char to int since there was no loss of precision and also opted against a cast in the other direction since the intent isn't as clear:

The language design committee] has chosen to provide an implicit conversion from bytes to chars, since the domain of one is completely contained by the other. Right now, however, [the runtime library authors] only provide Write methods which take chars and ints, which means that bytes print out as characters since that ends up being the best method. We can solve this either by providing more methods on the writer class or by removing the implicit conversion.

There is an argument for why the latter is the correct thing to do. After all, bytes really aren't characters. True, there may be a useful mapping from bytes to chars, but ultimately, 23 does not denote the same thing as the character with ASCII value 23, in the same way that the byte 23 denotes the same thing as the long 23. Asking [the library authors] to provide this additional method simply because of how a quirk in our type system works out seems rather weak.

http://blogs.msdn.com/b/ericlippert/archive/2009/10/01/why-does-char-convert-implicitly-to-ushort-but-not-vice-versa.aspx

Perhaps the fact that a committee thought long and hard about this might make you feel a little better :).

By the way, my comment above should have been '3'/51 not '3'/65 (typeo)
0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 38365342
I or somebody else didn't use Integer instead of int
What do you mean by that? This:

int x = 1;

Open in new window


versus this:

Int32 x = 1;

Open in new window


???
0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 38365373
@b_levitt

Looking back, perhaps I misinterpreted what you were saying. I missed that you included the single quotes within the double-quoted string. I thought you were saying that if someone passes a 3 as a character (e.g. char c = '3';) that they were actually meaning to pass the string "3" (e.g. string s = "3";).
0
 

Author Comment

by:DistillingExperts
ID: 38365412
@kaufmed,

I meant it: Int32 [] array = { 0, 1, 2, 3, '3' }, which I thought would prevent the error to happen, but has the same result. No compile time errors and getting "unexpected" outcomes.

@b_levitt, I would like to elaborate on your answer later with more time, even though I clear see your point. In a nutshell I would say that regardless of the committee, I would still lose my job in my scenario :-(.
0
 
LVL 11

Expert Comment

by:b_levitt
ID: 38365501
@DistillingExperts - I realize you see this as a potential to create a bug but there are easier ways to create issues that are much worse.  A financial system that uses floating point variables, c++ used to not catch assignment in an if statement, forgetting to include a where clause in a delete statement, etc.  Honestly your example stands out like a sore thumb even without an color coding, so I'm not terribly worried about it.  In fact, Java has the same feature.  Have you accidentally swapped a char for an int in your career so far?  If you haven't done it in 10 years I'm not sure you need to worry about it now.
0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 38365505
Well:

int[] array = { 0, 1, 2, 3, '3' };

Open in new window


...is exactly the same thing as:

Int32[] array = { 0, 1, 2, 3, '3' };

Open in new window


...because int is just an alias for Int32--the former being a convenience for the C/C++ folks; the latter being for the Java folks. There will be no issue if one person use int where another uses Int32 so far as the compiler is concerned. There would surely need to be an understanding by all of the programmers who touch the code, though.

I would hope that the person who decides to pass a char where an int was expected would be the one to lose their job! It basically comes down to knowing the language and/or tool in use. The language (and compiler) can save us from a lot of mistakes and headaches, but at the end of the day there are still some things that we as programmers must still know. I believe that is true of any programming language.
0
 

Author Comment

by:DistillingExperts
ID: 38365561
In fact, Java has the same feature.  Have you accidentally swapped a char for an int in your career so far?  If you haven't done it in 10 years I'm not sure you need to worry about it now.

More later again, but in 10 years (actually it is even before the Internet I come from), I haven't used Generics as a way to prevent run-time errors in compile-time so that I have been always very careful with my "power". It seems things didn't change much with Generics on this line of thinking.
0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 38365632
I don't really see generics "as a way to prevent run-time errors in compile-time" code. I suppose in a way they do that, but in my opinion generics are more about DRY. If you have a particular piece of logic that can be applied across multiple types, then it makes sense to create a generic method that will house the logic. Sure, you could just create one function that took in things of type Object--and certainly that's how .NET 1.x did things--but you trade performance for the luxury of (potentially) smaller code. Generics allow you to maintain type safety whilst exposing logic that would otherwise be duplicated had you created an overload to handle each applicable type.
0
 
LVL 17

Expert Comment

by:krakatoa
ID: 38365760
Yawn. :--¬0
0
 

Author Comment

by:DistillingExperts
ID: 38365780
Since you are back, could you elaborate more on this?
Where you require int, you use Integer, so you can type-check it. ;)
0
 
LVL 17

Assisted Solution

by:krakatoa
krakatoa earned 240 total points
ID: 38366008
0
 

Author Comment

by:DistillingExperts
ID: 38366697
@krakatoa

Please send me suggestions for C# not Java, even though it's easy to make the conversion, but it's nice to keep the post consistency too.
0
 
LVL 17

Expert Comment

by:krakatoa
ID: 38367012
I do not have anything to say about C# at all. I am in this Q from the Java TA. Sorry.
0
 

Author Comment

by:DistillingExperts
ID: 38367085
@krakatoa

Sorry, your inputs have been helpful. It is just that I am/was also a Java guy and don't want to mix things now I am getting deeper on the C# world.

Thank you.
0
 
LVL 17

Expert Comment

by:krakatoa
ID: 38367248
Perhaps it's a knee-jerk analogy, but in my (maybe) upper-intermediate level exposure to Generics, I see them as similar to an insurance policy - you don't want to see it 'kick-in' really; it costs 'money'; sometimes you don't get paid out fully; but without it you could be exposed to yet worse. You are forced to heed your own thinking a touch more, or challenge it, and try to help ensure it's correct.
0
 

Author Comment

by:DistillingExperts
ID: 38367641
@krakatoa:

Good analogy. I have the same opinion about insurance policy :-) and will try to think on that while using Generics.

@b_levitt:
I tried your implementation and got the compile-time error:
Error      1      The best overloaded method match for 'System.IComparable<T>.CompareTo(T)' has some invalid arguments      Sample.cs       (line 18)


Any help here?

    class Sample
    {
        static void Main()
        {
            Int32 [] array = { 0, 1, 2, 3, '3' };
            MakeAtLeast<int>(array, 2); // Change array to { 2, 2, 2, 3 }
            foreach (int i in array)
                Console.WriteLine(i); // Print results.
            Console.ReadKey(true);
        }


        //T[] list => my own comment: passing by reference
        static void MakeAtLeast<T>(T[] list, T lowest) where T : IComparable<T>
        {
            try {
                 for (int i = 0; i < list.Length; i++)
                    if ( ((IComparable<T>)list[i]).CompareTo((IComparable<T>)lowest) < 0 )
                        list[i] = lowest;
  
            }
            catch(InvalidCastException)
            {
                throw new ApplicationException(typeof(T).Name + " does not implement IComparable.  Consequently, 'lower' cannot be compared to the elements of 'list' in order to determine if a replacement is necessary.");
            }
        }
    }

Open in new window

0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 38367676
Don't cast lowest.
0
 

Author Comment

by:DistillingExperts
ID: 38367729
If I don't cast lowest, how do I do this: lowest < 0?
0
 
LVL 11

Expert Comment

by:b_levitt
ID: 38368136
kaufmed is correct - I didn't try to compile that and fudged it.  In your original example you're not comparing lowest to 0, you're comparing the return value from CompareTo:

http://msdn.microsoft.com/en-us/library/system.icomparable.compareto.aspx
0
 

Author Comment

by:DistillingExperts
ID: 38368361
You are right. Thanks. Now it's working

            try {
                 for (int i = 0; i < list.Length; i++)
                    if (((IComparable<T>)list[i]).CompareTo(lowest) < 0)
                        list[i] = lowest;
  
            }

Open in new window

0

Featured Post

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Container Orchestration platforms empower organizations to scale their apps at an exceptional rate. This is the reason numerous innovation-driven companies are moving apps to an appropriated datacenter wide platform that empowers them to scale at a …
In this post we will learn different types of Android Layout and some basics of an Android App.
This tutorial will introduce the viewer to VisualVM for the Java platform application. This video explains an example program and covers the Overview, Monitor, and Heap Dump tabs.
The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.
Suggested Courses
Course of the Month14 days, 21 hours left to enroll

839 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question