This project is read-only.

Avoiding Reflection By Assuming Generic Arguments to be of Type Object

Topics: C# Language Design
Apr 8, 2014 at 7:59 AM
I work a lot with generic parameters and reflection, and it would be nice to see a more comfortable merge between the two. The case I'm looking at now is related to Task<T>.

Consider the following case:
private async Task<object> Evaluate(object parameter)
{
   if(parameter is Task)
   {
        Task task = (Task)parameter;
        Type taskType = parameter.GetType();
        if(taskType.HasGenericParameters)
        {
             [await the task and return its Task.Result]
        }
        else
        {
             await task;
             return null;
        }
   }
   else
   {
         return parameter;
   }
}
When I hit "[await the task and return its Task.Result]," I know that we're dealing with a Task<T>. Of course, Task<T> has a property "Result" that the non-generic Task doesn't. I could use some reflection magic to get the value of that property after awaiting the task, but it would be fantastic if there were some way for me to say something like,
Task<?> taskWithResult = (Task<?>)task;
object ret = await taskWithResult;
return ret;
I presented this syntax more to be readable than as an actual direct suggestion, as I don't really have a good idea of how it could be implemented, but perhaps someone in the community will have some ideas.
Apr 8, 2014 at 8:07 PM
Couldn't you use dynamic for this?
dynamic taskWithResult = task;
object ret = await taskWithResult;
return ret;
Apr 8, 2014 at 9:03 PM
svick wrote:
Couldn't you use dynamic for this?
Interesting. I hadn't thought of that. I suppose that should do the trick, but I still wish there were some way to do this in a more strongly typed manner. Using dynamic for this feels somewhat hacky, which is alright, but given we have knowledge of the actual type that we're referencing here (Task<T>, although the same could apply to Nullable<T>, IEnumerable<T>, etc.), so it would be nice if IntelliSense and the compiler could use that knowledge to make everything a bit more efficient (I say that as someone who doesn't actually know how the compiler sees generic types, so maybe this wouldn't make it more efficient) and nice to use. I generally think of dynamic as being more useful for anonymous types, mostly just because the idea of runtime resolution is always a bit scary for me.
Apr 8, 2014 at 9:30 PM
It sounds like you effectively want to be able to cast to Task<object> even if the value is actually of type Task<string> or any other instantiation of Task<T>. In other words, you want Task<T> to be “covariant”.

This is problematic for at least two reasons that I can think of.

Firstly, let’s consider covariance on interfaces, which already exists. IEnumerable<string> converts to IEnumerable<object> because of covariance. However (this is the problem I want to point out), IEnumerable<int> does not convert to IEnumerable<object> even though int converts to object. The reason for this is that the latter conversion requires a boxing operation.

So what I think you effectively want is covariance on Task<T>, so that you can convert any Task<T> to Task<object>. The first problem I see is that it could be a Task<int>, or any other value type. Then the conversion to object requires boxing. For the reason that IEnumerable<int> does not convert to IEnumerable<object>, Task<int> will have the same difficulty.

The other problem is with methods that take a T as a parameter. Unfortunately I cannot demonstrate this on the example of Task<T> because it doesn’t have any such methods, but consider List<T>, which has a method Add(T). Now imagine I give you a List<Person>. But before I give it to you, I cast it to List<object>. Remember it’s still a List<Person> underneath. Now you see it as a List<object>, so you think it has a Add(object) method, so you call list.Add("foo") (i.e. you add a string). Clearly, the underlying List<Person> cannot take a string. This is why List<T> cannot be covariant.
Apr 9, 2014 at 4:46 PM
I believe he asks for wild card generics, like Java has.
Apr 10, 2014 at 12:55 AM
Sebazzz wrote:
I believe he asks for wild card generics, like Java has.
Yes, I suppose so. I've never actually used them in Java, since I tend to develop in C# as much as I can, but reading the Java documentation, that does look like what I'm talking about. The fact that my example used the exact syntax Java uses was mere luck.

Timwi, I see your points, definitely. The matter of boxing does make things more complicated, and I'm not sure of a particularly pretty way to accommodate the idea of the List<T>.Add(T) function, but I still feel like there must be some way. In fairness to the first point, my method accepts an object anyway, so it's not like we're completely avoiding boxing and unboxing by leaving out this specific implementation. Would boxing really be that significant of a problem? As for methods that depend on a generic property, it looks like Java recognizes the potential for bugs and raises a compile-time error.

Now, I'm the last person to waltz around declaring that C# should be more like Java. C# is an incredible language for a lot of reasons that Java isn't. And I recognize that the two are very different, and that what works in one might not conform to the other's technical or design specs. But that said, here goes, "if Java can do it, shouldn't it be theoretically possible in C#?" It looks like they've worked out the practical usage of these wildcard generics to the point that they at least make sense, so that brings us to three questions, the first of which being whether C# could theoretically handle them, given the way the compiler works and all that, the second being whether they would complicate the language or make it less readable, and the third being whether they would be useful.

As for the first question, I haven't the faintest idea. I'm not a language designer, if I haven't said that already. That's a matter for much better minds. As for the second, I think an implementation of them could be much cleaner than having to use dynamic or, god forbid, "actual reflection," with string literals and such. Sure, they open the potential for bad, awful-to-read code, but that's no real reason to completely ditch an idea. And as for the third, I definitely think they could be useful in some circumstances, hence why I started this thread in the first place.
Apr 10, 2014 at 2:38 AM
Edited Apr 10, 2014 at 2:39 AM
"if Java can do it, shouldn't it be theoretically possible in C#?"
In this case, not really. The reason Java is able to do that is they have a fundamentally different model of generics than .NET does. Java does generics via type erasure: when you have List<Integer>, the compiler actually replaces all the type parameters with Object while it's building. At runtime, a List<Integer> actually is a List<Object> -- and could even contain non-integers within it! This makes List<?> easy, because the compiler doesn't need to actually know what ? is -- it's always an object under the covers. In .NET we have a different type system where the generic parameter is actually baked in to the IL and enforced at runtime. And, as was described earlier, in general Foo<T> isn't covariant, as long as there's some setter or method that accepts a T.

That's not to say that it can't be done, but C#'s design here is forced by the CLR, which simply chose a completely different direction for generics. The sad part here, probably, is Task<T> doesn't have a covariant ITask<out T> interface that it implements. Then you could cast the type to ITask<object> and you'd have exactly what you want.

Looking at the original question, it's not clear to me why "object parameter" is being used in the first place. I recognize it's only an example, but often adding stronger type guarantees somewhere else makes a want for this unnecessary. For example, we know it's a task coming in, so why can't instead write "Task<T> parameter" and push the cast somewhere else that probably knows what the type is?
Apr 10, 2014 at 5:55 AM
Edited Apr 10, 2014 at 5:57 AM
That's interesting. I didn't know that about Java. It sounds so prone to bugs to store everything as object.

The specific example I gave at the original question was actually pretty realistic to what I'm doing. It's an edge case by any measure, but I'm writing a helper function to route and handle requests and return values, somewhat similar in nature to Web Api. Long story short, I'm getting an object by invoking a method with reflection and I have to determine whether that object is a Task. Specifically, my function accepts a Type[] and sorts out the request to find the appropriate method in the appropriate type and returns the method's value, serialized. Since I'm calling MethodInfo.Invoke, I have an object that I need to deal with. If that object is a Task, I want to await it and serialize the result (or null if it doesn't have one, as I showed in my example). If it isn't a Task, I don't have to worry about that and I can go straight to serialization.

It's far from a serious problem not having those kinds of wildcards. It just seems like it could be useful from time to time.
Apr 10, 2014 at 10:20 AM
it sound like you want some easy way to use "Is/as" or cast with generic type. such as (a is Task<?>) or (a as Task<?>) or (Task<?>)a. but I am not sure whether that is possible at compile time, the ? is only known at runtime so, you either need to propagate that T to the point where it is known at compile time, or I think something like reflection or dynamic that gets runtime type info is needed for what you want to do.

dynamic seems exactly what you want.
Apr 10, 2014 at 2:51 PM
Edited Apr 10, 2014 at 2:54 PM
jasonmalinowski wrote:
The sad part here, probably, is Task<T> doesn't have a covariant ITask<out T> interface that it implements. Then you could cast the type to ITask<object> and you'd have exactly what you want.
Why doesn't it? Isn't adding an interface a change that maintains backwards compatibility?

I've posted this idea on UserVoice.
Apr 13, 2014 at 11:46 AM
Edited Apr 13, 2014 at 11:46 AM
I just thought of the ITask<out T> idea too and came here to suggest it. ;-) The CLR could add such an interface and have Task<T> implement it without a breaking change. Then your code would be much simpler:
        if (parameter is ITask<object>)
             return await (ITask<object>) parameter;
        else
        {
             await (Task) parameter;
             return null;
        }
Oct 16, 2014 at 1:35 PM
I have implemented an ITask interface which could be used to accomplish the cast to ITask<object>. See https://www.github.com/jam40jeff/ITask
Oct 16, 2014 at 4:17 PM
Edited Oct 16, 2014 at 4:21 PM
Why can't yiu use method overloading? One that takes.task as the type of the method parameterx another generic t task, and another that takes object.
Oct 16, 2014 at 7:19 PM
svick wrote:
Why doesn't it? Isn't adding an interface a change that maintains backwards compatibility?
I would think so. Another interface I'd like to see would be to have every delegate include a runtime-generated interface type nested within it so that e.g. Function<int, double, string>.IInvokable would be an interface that includes member string Invoke(int p1, double p2);, which would be automatically implemented by Function<int, double, string>. Many methods which accept a delegate type would be just as happy with an interface type, but many closures could implement the necessary interfaces directly without having to create delegates for the purpose. Further, if a method were to accept a generic parameter constrained as a suitable IInvokable, a compiler may be able to convert a lambda into a structure rather than a class, thus allowing code which would have required two temporary object allocations (one for a closure and one for a delegate) to instead require zero.
Oct 17, 2014 at 7:32 PM
jasonmalinowski wrote:
Looking at the original question, it's not clear to me why "object parameter" is being used in the first place. I recognize it's only an example, but often adding stronger type guarantees somewhere else makes a want for this unnecessary. For example, we know it's a task coming in, so why can't instead write "Task<T> parameter" and push the cast somewhere else that probably knows what the type is?
Jason's question seems most relevant here. If you have a Task in hand that you are passing to this parameter, then you can do this:
static void Main(string[] args)
{
    Task<object> taskInt = Evaluate(Task.FromResult(1));
    Task<object> taskString = Evaluate(Task.FromResult(string.Empty));
    Task<object> taskNull = Evaluate(new Task(delegate { }));
}

public static async Task<object> Evaluate<T>(Task<T> param)
{
    return await param;
}
public static async Task<object> Evaluate(Task param)
{
    await param;
    return null;
}