简体   繁体   中英

dynamic and generics in C#

As discovered in C 3.5, the following would not be possible due to type erasure: -

int foo<T>(T bar)
{
    return bar.Length; // will not compile unless I do something like where T : string
}

foo("baz");

I believe the reason this doesn't work is in C# and java, is due to a concept called type erasure, see http://en.wikipedia.org/wiki/Type_erasure .

Having read about the dynamic keyword, I wrote the following: -

int foo<T>(T bar)
{
    dynamic test = bar;
    return test.Length;
}

foo("baz"); // will compile and return 3

So, as far as I understand, dynamic will bypass compile time checking but if the type has been erased, surely it would still be unable to resolve the symbol unless it goes deeper and uses some kind of reflection?

Is using the dynamic keyword in this way bad practice and does this make generics a little more powerful?

dynamics and generics are 2 completely different notions. If you want compile-time safety and speed use strong typing (generics or just standard OOP techniques such as inheritance or composition). If you do not know the type at compile time you could use dynamics but they will be slower because they are using runtime invocation and less safe because if the type doesn't implement the method you are attempting to invoke you will get a runtime error.

The 2 notions are not interchangeable and depending on your specific requirements you could use one or the other.

Of course having the following generic constraint is completely useless because string is a sealed type and cannot be used as a generic constraint:

int foo<T>(T bar) where T : string
{
    return bar.Length;
}

you'd rather have this:

int foo(string bar)
{
    return bar.Length;
}

I believe the reason this doesn't work is in C# and java, is due to a concept called type erasure, see http://en.wikipedia.org/wiki/Type_erasure .

No, this isn't because of type erasure. Anyway there is no type erasure in C# (unlike Java): a distinct type is constructed by the runtime for each different set of type arguments, there is no loss of information.

The reason why it doesn't work is that the compiler knows nothing about T , so it can only assume that T inherits from object , so only the members of object are available. You can, however, provide more information to the compiler by adding a constraint on T. For instance, if you have an interface IBar with a Length property, you can add a constraint like this:

int foo<T>(T bar) where T : IBar
{
    return bar.Length;
}

But if you want to be able to pass either an array or a string, it won't work, because the Length property isn't declared in any interface implemented by both String and Array ...

No, C# does not have type erasure - only Java has.

But if you specify only T, without any constraint, you can not use obj.Lenght because T can virtually be anything.

foo(new Bar());

The above would resolve to an Bar-Class and thus the Lenght Property might not be avaiable. You can only use Methods on T when you ensure that T this methods also really has. (This is done with the where Constraints.)

With the dynamics, you loose compile time checking and I suggest that you do not use them for hacking around generics.

In this case you would not benefit from dynamics in any way. You just delay the error, as an exception is thrown in case the dynamic object does not contain a Length property. In case of accessing the Length property in a generic method I can't see any reason for not constraining it to types who definately have this property.

"Dynamics are a powerful new tool that make interop with dynamic languages as well as COM easier, and can be used to replace much turgid reflective code. They can be used to tell the compiler to execute operations on an object, the checking of which is deferred to runtime.

The great danger lies in the use of dynamic objects in inappropriate contexts, such as in statically typed systems, or worse, in place of an interface/base class in a properly typed system."

Qouted From Article

Thought I'd weigh-in on this one, because no one clarified how generics work "under the hood". That notion of T being an object is mentioned above, and is quite clear. What is not talked about, is that when we compile C# or VB or any other supported language, - at the Intermediate Language (IL) level (what we compile to) which is more akin to an assembly language or equivalent of Java Byte codes, - at this level, there is no generics! So the new question is how do you support generics in IL? For each type that accesses the generic, a non-generic version of the code is generated which substitutes the generic(s) such as the ubiquitous T to the actual type it was called with. So if you only have one type of generic, such as List<>, then that's what the IL will contain. But if you use many implementation of a generic, then many specific implementations are created, and calls to the original code substituted with the calls to the specific non-generic version. To be clear, a MyList used as: new MyList(), will be substituted in IL with something like MyList_string().

That's my (limited) understanding of what's going on. The point being, the benefit of this approach is that the heavy lifting is done at compile-time, and at runtime there's no degradation to performance - which is again, why generic are probably so loved used anywhere, and everywhere by .NET developers.

On the down-side? If a method or type is used many times, then the output assembly (EXE or DLL) will get larger and larger, dependent of the number of different implementation of the same code. Given the average size of DLLs output - I doubt you'll ever consider generics to be a problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM