Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeUtil.ToCSharpString(this Type) does not handle nested generic types correctly #404

Closed
stakx opened this issue May 31, 2018 · 3 comments · Fixed by #407
Closed

TypeUtil.ToCSharpString(this Type) does not handle nested generic types correctly #404

stakx opened this issue May 31, 2018 · 3 comments · Fixed by #407

Comments

@stakx
Copy link
Member

stakx commented May 31, 2018

Migrated from castleproject/Core#365.


@pmg23 wrote:

The useful extension method TypeUtil.ToCSharpString(this Type) does not correctly deal with types nested within generic types [as of Core 4.2.1]. For example:

internal class A<T1>
{
    internal class B {}
    internal class C<T2> {}
}

Assert.That(typeof(A<string>.B).ToCSharpString(),
        Is.EqualTo("A<String>.B")); // FAIL: actually "A<·T1·>.B<String>"
Assert.That(typeof(A<string>.C<int>).ToCSharpString(),
        Is.EqualTo("A<String>.C<Int32>")); // FAIL: actually "A<·T1·>.C<String, Int32>"

@stakx replied:

(Shameless advertisement:)

Type name formatting is surprisingly complex because there are quite a few corner cases to deal with. I happen to have written a small library recently (stakx/TypeNameFormatter) that would solve this problem. It's available as a source code NuGet package.


@stakx replied:

That being said, I cannot seem to find a method TypeUtil.ToCSharpString(this Type) in Castle Core (this repository). Did you mean this method in Castle Windsor (castleproject/Windsor)?

@pmg23
Copy link

pmg23 commented May 31, 2018

Sorry, I should have given the full name: Castle.Core.Internal.TypeUtil.ToCSharpString(this Type); which (as @stakx kindly points out) is part of Windsor. We observe this behaviour in Windsor 4.1.0.

@ghost
Copy link

ghost commented May 31, 2018

Yep, looks like unbound generic types are being passed recursively through the AppendGenericParameters method here.

@stakx
Copy link
Member Author

stakx commented Jun 5, 2018

Not quite. The thing is this:

  • Under the hood, nested types "inherit" the generic type parameters of their enclosing type(s). This explains why the type C from A<String>.C<Int32> ends up having two generic type arguments in the formatted output. (It's perhaps easier to think of C as an independent type instead of somehow being connected to A to understand why. Note though that its IL name is still C`1 despite having two parameters.)

  • Reflecting over the enclosing type will return the type definition A<T1>, i.e. the type argument String is lost during that call. This explains why you end up with A<T1>... instead of A<String>.

To fix the formatting of nested generic types, you'd have to:

  • Figure out how many own parameters the nested type declares (let's call this number n). For CLS-compliant types, this is equal to the number of generic type args/params minus that of the enclosing type.

  • Only use the last n generic params/args for the nested type. The remaining (leading) args need to be passed recursively to the formatting of the enclosing type(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants