Generic array is not recognized as correct parameter type

  • Thread starter Hans-Jürgen Philippi
  • Start date
H

Hans-Jürgen Philippi

Hi all,

i've written a generic class (using VS 2005 and .NET 2.0) with a T type
parameter and a constraint (naming an interface):

public class MyGenericClass<T> where T : IMyBaseType {
...
}


Inside this class I'm going to call an external function that expects a
parameter with types implementing IMyBaseType:

[a] myFunction(IMyBaseType SingleValue) {...}
and an overload:
myFunction(IMyBaseType[] ArrayValue) {...}


Assuming myItem is of type T and myItems of type T[], thereby I have calls
inside the class that look like this:

[a] myFunction(myItem)
or
myFunction(myItems)


The point is that [a] works but doesn't, the compiler says he "cannot
convert from T[] to IMyBaseType". Obviously he doesn't event recognize that
actually a handling of/conversion to IMyBaseType[] was required resp. the
second overload is meant...
I think I've seen this before and it may have something to do with the
boxing related to the array handling - but I'm not sure how to circumvent
this.

Any ideas?

Greetings,
Hans
 
M

Marc Gravell

How about:

myFunction<T>(T[] arrayValue) where T : IMyBaseType {...}

or perhaps even merge the two functions:

myFunction<T>(params T[] arrayValue) where T : IMyBaseType {...}

Now you can call

myFunction(someInstance);
myFunction(someArrayOfInstance);
myFunction(someInstance, someOtherInstance, someThirdInstance);

Note that the compiler will infer the T from context.

Additionally, perhaps consider IList<T> or similar in place of T[] -
this will give you even greater flexibility (although you can only use
"params" with arrays).

Marc
 
H

Hans-Jürgen Philippi

This has to be fate: I just told someone in the newsgroup to try the 'as'
keyword with event handling and the simple cast seems to save my day, too.
:)
When I change the mentioned call

myFunction(myItems)
to
myFunction(myItems as IMyBaseType[])

it works, as far as I can see. Although I'm asking myself why the compiler
is not able to recognize the array type validity itself, based on the
constraint. In the end, myItems is of type T[] what in turn *must* be
IMyBaseType[], right?


Anyway. Just wanted to let you know and in case someone else is going to
google for something similar...

Greetings,
Hans
 
H

Hans-Jürgen Philippi

Hi Marc,

Marc Gravell said:
How about:

myFunction<T>(T[] arrayValue) where T : IMyBaseType {...}

Hey, this is a good idea since I need the myFunction method in different
contexts and not only with IMyBaseType. I will definitely try this out!


Do you know if there is a way to combine constraints like:

where T : IMyBaseType or IMyOtherType or ...?


Although I think it would be better to find/create a common base interface
of IMyBaseType and IMyOtherType and use:

where T : IMyCommonType


But I was in situations where I'd like to have some sort of "constraint
combination" with the compiler detecting common/equally typed interface
members:

myItem.OnePropertyAvailableEverywhere = "Hi!";

Would this make sense in your opinion?


Greetings,
Hans
 
J

Jon Skeet [C# MVP]

This has to be fate: I just told someone in the newsgroup to try the 'as'
keyword with event handling and the simple cast seems to save my day, too.
:)
When I change the mentioned call

myFunction(myItems)
to
myFunction(myItems as IMyBaseType[])

it works, as far as I can see. Although I'm asking myself why the compiler
is not able to recognize the array type validity itself, based on the
constraint. In the end, myItems is of type T[] what in turn *must* be
IMyBaseType[], right?


Nope. Consider a struct which implements IMyBaseType. An array of that
struct isn't convertible to an array of IMyBaseType. Here's an example
using int/IConvertible:

using System;

class Test
{
static void Main()
{
object o = 5;
Console.WriteLine (o is IConvertible);
o = new int[] { 5 };
Console.WriteLine (o is IConvertible[]);
}
}

Jon
 
H

Hans-Jürgen Philippi

Hi Jon,

Jon Skeet said:
Although I'm asking myself why the compiler
is not able to recognize the array type validity itself, based on the
constraint. In the end, myItems is of type T[] what in turn *must* be
IMyBaseType[], right?

Nope. Consider a struct which implements IMyBaseType. An array of that
struct isn't convertible to an array of IMyBaseType.

Ahem - why not?
Here's an example using int/IConvertible:

using System;

class Test
{
static void Main()
{
object o = 5;
Console.WriteLine (o is IConvertible);
o = new int[] { 5 };
Console.WriteLine (o is IConvertible[]);
}
}

Well, I understand that in the last line, o -as an array- is neither
IConvertible nor IConvertible[]. This is because the compiler "sees" only
the outer reference to an object type.
But in my case, he already knows I'm having an array! So if he looked at the
inner array member type, he could recognize it actually *is* an array of
IConvertible types. If you changed your example to:

object[] o2 = new object[] { 5 };
Console.WriteLine (o2 is IConvertible[]); //False
Console.WriteLine (o2[0] is IConvertible); //True!


In this way he actually could recognize that the required assignment will
work. Or am I wrong?


Greetings,
Hans
 
J

Jon Skeet [C# MVP]

Ahem - why not?

An interface is a reference type - so an IFoo[] must have each element
as a reference. That's not the case for FooImpl[] where FooImpl is a
value type.
class Test
{
static void Main()
{
object o = 5;
Console.WriteLine (o is IConvertible);
o = new int[] { 5 };
Console.WriteLine (o is IConvertible[]);
}
}

Well, I understand that in the last line, o -as an array- is neither
IConvertible nor IConvertible[].

No, "as" is done as a runtime check, not a compile-time check. If you
change the third line to:

o = new string[] {"hello"};

it will print True.
This is because the compiler "sees" only
the outer reference to an object type.

The compiler is irrelevant at that point, beyond checking that it
*might* be valid.
But in my case, he already knows I'm having an array! So if he looked at the
inner array member type, he could recognize it actually *is* an array of
IConvertible types. If you changed your example to:

object[] o2 = new object[] { 5 };
Console.WriteLine (o2 is IConvertible[]); //False
Console.WriteLine (o2[0] is IConvertible); //True!

In this way he actually could recognize that the required assignment will
work. Or am I wrong?

It's still only working it out at runtime - and the assignment is
still invalid. You *cannot* assign an int[] to an IConvertible[] -
they're just not compatible types.

Jon
 
M

Marc Gravell

Do you know if there is a way to combine constraints like:
where T : IMyBaseType or IMyOtherType or ...?
There isn't one in C# 2, other than to have two methods.
In C# 3 an option here might be extension methods? i.e. you could
create an extension method for IMyBaseType and an extension method for
IMyOtherType, so that any object of either type appears to support
this functionality.
myItem.OnePropertyAvailableEverywhere = "Hi!";
Would this make sense in your opinion?
A form of duck-typing? It isn't supported in quite the way that you
want (although I belive LINQ does some level of duck-typing under the
hood?).

Marc
 
J

Jon Skeet [C# MVP]

There isn't one in C# 2, other than to have two methods.
In C# 3 an option here might be extension methods? i.e. you could
create an extension method for IMyBaseType and an extension method for
IMyOtherType, so that any object of either type appears to support
this functionality.

No, that wouldn't help - the compiler would have to know at compile-
time which method to call.
A form of duck-typing? It isn't supported in quite the way that you
want (although I belive LINQ does some level of duck-typing under the
hood?).

Nah - there's no duck-typing going on in LINQ; it's all static, but
implicitly typed local variables just make life a bit easier, as do
extension methods.

Jon
 
M

Marc Gravell

Nah - there's no duck-typing going on in LINQ
Well, you'd know better than me - but I was mereely going from what I
have read; I will try to form a clearer understanding of this before
repearing such...

http://blogs.msdn.com/kcwalina/archive/2007/07/18/DuckNotation.aspx
<q>The query operators use duck typing to determine whether they can
operate on a type or not.</q>

http://www.theserverside.net/news/thread.tss?thread_id=46917
<q>It's a form of Duck Typing - consumers of LINQ implementations only
No, that wouldn't help - the compiler would have to know at compile-
time which method to call.
I have investigated, and you are quite correct. The following is
ambiguous between ExtendsA and ExtendsB, even though it can only
possibly satisfy ExtendsA due to the constraints. I didn't see that
coming! My mistake.

using System;
interface IIntA { string MemberA { get; } }
interface IIntB { string MemberB { get; } }
class ClassA : IIntA {
public string MemberA { get { return "A"; } }
}
class Program {
static void Main() {
ClassA[] a = new ClassA[5];
a.SomeMethod(); // CS0121
}
}
static class ExtendsA {
public static void SomeMethod<T>(this T[] data)
where T : IIntA { }
}
static class ExtendsB {
public static void SomeMethod<T>(this T[] data)
where T : IIntB { }
}

Marc
 
J

Jon Skeet [C# MVP]

Well, you'd know better than me - but I was mereely going from what I
have read; I will try to form a clearer understanding of this before
repearing such...

http://blogs.msdn.com/kcwalina/archive/2007/07/18/DuckNotation.aspx
<q>The query operators use duck typing to determine whether they can
operate on a type or not.</q>

Okay, I see what you're getting at now. Yes, query expression
translation doesn't require a particular interface to be implemented.
However, it's still all determined at compile-time. I guess I'm used
to duck-typing referring to more of a runtime decision :) It's one of
those situations where "duck-typing" isn't a sufficiently well-defined
term to say one way or the other. I believe we both understand what
actually happens though, which is the important bit.

(Note that this is similar to how foreach has always worked - you
don't actually have to implement IEnumerable to be "foreach-able".
Likewise it's how collection initializers are done in C# 3, using the
"Add" method.)

I'd actually argue that Krzysztof's description is misleading, as he
refers to a wikipedia article which starts off:

"In computer programming, duck typing is a style of dynamic typing in
which an object's current set of methods and properties determines the
valid semantics, rather than its inheritance from a particular class."

This is *not* dynamic typing (it's all done at compile-time) and it's
not the *object's* current set of methods/properties which determine
the valid semantics, it's the compile-time set of available methods.

However, it has a similar *feel* to duck-typing in that the matches
are performed based on method names rather than specific interfaces.
I have investigated, and you are quite correct. The following is
ambiguous between ExtendsA and ExtendsB, even though it can only
possibly satisfy ExtendsA due to the constraints. I didn't see that
coming! My mistake.

Yikes - bringing in type inference and constraints certainly makes
things even more complicated. This is the kind of situation which is
basically best avoided! I know the rules reasonably well, but I'd
certainly have to consult the spec to find out the exact order of
application. (Are constraints considered before or after overloading
determines the applicable methods, for instance? Looks like it's
after.)

Jon
 
M

Marc Gravell

Yikes - bringing in type inference and constraints certainly makes
things even more complicated.

Yes - after consideration I think I agree. For this corner-case
example there is only one logical result, but it is at the top of a
very slippery slope, and the compiler (not to mention the language
spec) would get very messy if it tried to account for everything.

Marc
 
J

Jon Skeet [C# MVP]

Yes - after consideration I think I agree. For this corner-case
example there is only one logical result, but it is at the top of a
very slippery slope, and the compiler (not to mention the language
spec) would get very messy if it tried to account for everything.

In particular, it gets even nastier when you bring in the fact that
lambda expressions can only be validated against a particular set of
input parameter types, which may be implicit - and they can determine
the output type. You could do all kinds of things if you wanted to be
as forgiving as possible :)

Jon
 

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