icsharpcode / ILSpy

.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
20.73k stars 3.29k forks source link

ICSharpCode.Decompiler.TypeSystem.IType property values should match System.Reflection.Type property values #3170

Open cwellsx opened 4 months ago

cwellsx commented 4 months ago

I'm viewing type information in two ways:

I notice the your IType API is very similar to the Type API but not identical. And I don't know whether the difference is on purpose but it troubles me they're not quite interchangeable.

For reference, here's my record into which I load the property values:

    public record TypeId(
        string? AssemblyName,
        string? Namespace,
        string Name,
        Values<TypeId>? GenericTypeArguments,
        TypeId? DeclaringType
        );

Here's my initializing that from a System.Reflection.Type instance

        static TypeId GetTypeId(Type type)
        {
            TypeId[]? GetGenericTypeArguments()
            {
                var types = type.GenericTypeArguments;
                return (types.Length == 0) ? null : types.Select(GetTypeId).ToArray();
            }
            return new TypeId(
                AssemblyName: type.Assembly.GetName().Name,
                Namespace: type.Namespace,
                Name: type.Name,
                GenericTypeArguments: GetGenericTypeArguments(),
                DeclaringType: type.DeclaringType != null ? GetTypeId(type.DeclaringType) : null
            );
        }

Here's my initializing that from your IType instance

        static TypeId Transform(this IType type)
        {
            var elementType = (type as TypeWithElementType)?.ElementType ?? type;
            var nameSuffix = (type as TypeWithElementType)?.NameSuffix ?? string.Empty;
            var assemblyName = elementType.GetDefinition()?.ParentModule?.AssemblyName;
            // want a name like "KeyValuePair`2[]" (i.e. with generic arity appended) and not just "KeyValuePair[]"
            var name = elementType.GetDefinition()?.MetadataName ?? type.Name;
            // we have the name of the element not the type, so add the suffix
            name += nameSuffix;

            return new TypeId(
                assemblyName,
                type.Namespace == string.Empty ? null : type.Namespace,
                name,
                type.TypeArguments.Select(Transform).ToArrayOrNull(),
                type.DeclaringType?.Transform()
               );
        }

In summary the differences I've found so far (after only one day of test-and-debugging) are as follows.

  1. The AssemblyName when the type is an array -- you correctly (i.e. same as Type) display the name e.g. Foo[], and the namespace (of Foo), but not the name of the assembly from which Foo is loaded -- so instead I must fetch that from your ElementType property.
  2. The Namespace, when there isn't one -- you return an empty string, Type returns null.
  3. Whether the name of the type is e.g. KeyValuePair or KeyValuePair2`