Generics - where with comparable - help please

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

DistillingExpertsAsked:
Who is Participating?
 
käµfm³d 👽Commented:
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
 
RyanProject Engineer, ElectricalCommented:
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
 
käµfm³d 👽Commented:
It is a constraint. In this case it means that any type used in the place of T must implement the IComparable interface.
0
Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

 
AndyAinscowFreelance programmer / ConsultantCommented:
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
 
b_levittCommented:
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
 
DistillingExpertsAuthor Commented:
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
 
DistillingExpertsAuthor Commented:
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
 
DistillingExpertsAuthor Commented:
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
 
b_levittCommented:
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
 
b_levittCommented:
In your array example, there is an inherent cast of your '3' to integer 51 (ascii).
0
 
AndyAinscowFreelance programmer / ConsultantCommented:
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
 
b_levittCommented:
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
 
krakatoaCommented:
No, int and char are interchangeable. It's not a bug.
0
 
käµfm³d 👽Commented:
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
 
krakatoaCommented:
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
 
RyanProject Engineer, ElectricalCommented:
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
 
AndyAinscowFreelance programmer / ConsultantCommented:
@MrBullwinkle - this is C# not C or C++
0
 
krakatoaCommented:
Well; only need this, to illustrate the point really:

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

Open in new window

0
 
DistillingExpertsAuthor Commented:
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
 
DistillingExpertsAuthor Commented:
b_levitt, thank you for the code. That made me feel compelling to use Generics all the time.
0
 
krakatoaCommented:
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
 
DistillingExpertsAuthor Commented:
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
 
krakatoaCommented:
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
 
b_levittCommented:
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
 
käµfm³d 👽Commented:
@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
 
DistillingExpertsAuthor Commented:
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
 
b_levittCommented:
@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
 
b_levittCommented:
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
 
b_levittCommented:
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
 
käµfm³d 👽Commented:
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
 
käµfm³d 👽Commented:
@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
 
DistillingExpertsAuthor Commented:
@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
 
b_levittCommented:
@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
 
käµfm³d 👽Commented:
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
 
DistillingExpertsAuthor Commented:
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
 
käµfm³d 👽Commented:
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
 
krakatoaCommented:
Yawn. :--¬0
0
 
DistillingExpertsAuthor Commented:
Since you are back, could you elaborate more on this?
Where you require int, you use Integer, so you can type-check it. ;)
0
 
krakatoaCommented:
0
 
DistillingExpertsAuthor Commented:
@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
 
krakatoaCommented:
I do not have anything to say about C# at all. I am in this Q from the Java TA. Sorry.
0
 
DistillingExpertsAuthor Commented:
@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
 
krakatoaCommented:
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
 
DistillingExpertsAuthor Commented:
@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
 
käµfm³d 👽Commented:
Don't cast lowest.
0
 
DistillingExpertsAuthor Commented:
If I don't cast lowest, how do I do this: lowest < 0?
0
 
b_levittCommented:
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
 
DistillingExpertsAuthor Commented:
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
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.