GithubHelp home page GithubHelp logo

stakx / typenameformatter Goto Github PK

View Code? Open in Web Editor NEW
32.0 4.0 4.0 142 KB

A small .NET library for formatting type names à la C#.

License: MIT License

C# 100.00%
dotnet dotnet-standard reflection formatter types csharp

typenameformatter's Introduction

TypeNameFormatter

TypeNameFormatter is a small .NET library for formatting type names à la C#.

NuGet badge AppVeyor AppVeyor tests Codecov

What is this good for?

Have you ever stumbled over the cryptic formatting of Type objects?

var someType = typeof(IEnumerable<int[]>);

Console.WriteLine(someType);
// => System.Collections.Generic.IEnumerable`1[System.Int32[]]

If you'd rather see something that looks more like a C# type name, then this library might be for you:

using TypeNameFormatter;

var someType = typeof(IEnumerable<int[]>);

Console.WriteLine(someType.GetFormattedName());
// => IEnumerable<int[]>

Formatting any Type involves more special cases than you might expect (such as generic types, nested types, multi-dimensional and jagged arrays, by-reference and pointer types). This library deals with all of those, so that you don't have to.

How do I use it?

By importing the TypeNameFormatter namespace, the following extension methods become available:

  • stringBuilder.AppendFormattedName(Type type, [TypeNameFormatOptions options]):
    Appends a C#-formatted type name to the given StringBuilder.

  • type.GetFormattedName([TypeNameFormatOptions options]):
    Returns a C#-formatted type name as a string. (This is a convenience method that does exactly the same as the above, using a throw-away StringBuilder.)

Both methods allow you to specify any combination of the following TypeNameFormatOptions flags:

  • Namespaces:
    Namespaces should be included. (For example, System.Action instead of Action.)

  • NoAnonymousTypes: Anonymous types should not have their "display class" name transformed to a more legible syntax. (For example, <>f__AnonymousType5<string, int> instead of {string Name, int Count}.)

  • NoGenericParameterNames:
    Parameter names of an open generic type should be omitted. (For example, IEnumerable<> instead of IEnumerable<T>. Note that this setting does not affect closed generic types; their arguments are always included.)

  • NoKeywords:
    Primitive types should not be mapped to their corresponding C# language keywords. (For example, Int32 instead of int.)

  • NoNullableQuestionMark: Nullable types should not be formatted using C# question mark syntax. (For example, Nullable<int> instead of int?.)

  • NoTuple: Value tuple types should not be formatted using C# tuple syntax. (For example, ValueTuple<bool, int> instead of (bool, int).)

But it doesn't format <some type> correctly!

If you think you've found a bug, please raise an issue so it can be looked into. (Make sure to mention the type that doesn't get formatted as expected.)

Alternatives

  • If you're targeting the .NET Framework, you can use good old System.CodeDom (which isn't particularly fast, however):

    using Microsoft.CSharp;
    using System.CodeDom;
    
    static string GetFormattedName(this Type type)
    {
        using (var provider = new CSharpCodeProvider())
        {
            var typeReference = new CodeTypeReference(type);
            return provider.GetTypeOutput(typeReference);
        }
    }
  • You could perhaps use Microsoft's .NET Compiler Platform (Roslyn), but that is a large library that can do much more than is needed.

Advanced usage

Configuration knobs for the source code distribution

The TypeNameFormatter.Sources NuGet package comes with a few MSBuild properties that you can set inside your project file (inside a <PropertyGroup>):

  • <TypeNameFormatterInternal>:
    This property determines the visibility of the types provided by TypeNameFormatter:

    • If set to True (the default), they are declared internal.
    • If set to False, they are declared public.
  • <TypeNameFormatterProjectNodeName>:
    This property determines the name under which TypeNameFormatter's single .cs file will appear in e.g. Visual Studio's Solution Explorer:

    • If set to TypeNameFormatter.cs (the default), a hidden linked file by that name will be added to your project's root.
    • If set to any other relative file path, a visible linked file will be added to your project.

For example:

<Project …>
  …
  <PropertyGroup>
    <!-- Make TypeNameFormatter's types `public` instead of `internal`: -->
    <TypeNameFormatterInternal>False<TypeNameFormatterInternal>

    <!-- Make a linked file `TypeNameFormatter.cs` show up in Solution Explorer
         under a folder node named `Utilities`: -->
    <TypeNameFormatterProjectNodeName>Utilities\TypeNameFormatter.cs</TypeNameFormatterProjectNodeName>
  </PropertyGroup>
  …
</Project>

typenameformatter's People

Contributors

stakx avatar kzu avatar

Stargazers

 avatar Denis avatar Cédric Luthi avatar smartcaveman avatar Mikael Olsson avatar Ben Mazzarol avatar Ahmed Şeref avatar Shoaib Shakeel avatar Alexey Shokov avatar Ole-Kristian Hagen avatar Jamie Walters avatar Robert Ștefan Stănescu avatar JPVenson avatar Andrey Leskov avatar  avatar Julian Verdurmen avatar Robert Byrne avatar  avatar  avatar  avatar ievo avatar  avatar pedoc avatar  avatar Alex Stek avatar 0x7FFFFFFFFFFFFFFF avatar  avatar Acoris avatar Ulysses avatar Anton Firszov avatar Matt Grimwade avatar  avatar

Watchers

 avatar Alexey Shokov avatar James Cloos avatar  avatar

typenameformatter's Issues

Idea: Add support for formatting parameter types

This library currently only looks at Type, but for parameter types there is some additional useful information at the ParameterInfo level:

  • whether params was specified;

  • allows one to distinguish between in, ref, and out for by-ref parameters.

This lies a little outside the originally planned feature area of this library, but might nonetheless be useful to have.

Constructed generic types using "captured" generic type parameters leads to incomplete output

class Cup<T>
{
    public T[] Array;
    public (bool, T) Tuple;
}

Current, actual behavior:

typeof(Cup<>).GetField("Array").FieldType.GetFormattedName();
// => []

typeof(Cup<>).GetField("Tuple").FieldType.GetFormattedName();
// => (bool, )

Expected behavior:

In cases where a generic type parameter is used outside its generic type definition to construct another type, it would probably make sense to unconditionally imply TypeNameFormatOptions.GenericParameterNames:

typeof(Cup<>).GetField("Array").FieldType.GetFormattedName();
// => T[]

typeof(Cup<>).GetField("Tuple").FieldType.GetFormattedName();
// => (bool, T)

Or, with other words, it is only in generic type definitions that parameter names may be omitted.

Add .NET 2.0, 3.5, 4.0, and .NET Standard 1.0 as additional framework targets

There's no reason why this library couldn't support much earlier versions .NET. The necessary reflection bits should be available on most common .NET platform targets.

  • .NET 2.0 might require a different public API since its BCL doesn't have the [Extension] custom attribute.
  • .NET Standard <2.0 will require some conditional compilation magic because it requires .GetTypeInfo() in some places.

Regression in 1.0.0-beta2: Closed generic type enclosed in non-generic type causes exception

class Outer
{
    public class Inner<T> { }
}

typeof(Outer.Inner<int>).GetFormattedName();  // *boom*

The problem lies here:

var isConstructedGenericType = IsConstructedGenericType(typeWithGenericTypeArgs);
if (isConstructedGenericType)
{
// Nullable value types (excluding the open generic Nullable<T> itself):
if (IsSet(TypeNameFormatOptions.NoNullableQuestionMark, options) == false && type.GetGenericTypeDefinition() == typeof(Nullable<>))

This only checks whether the innermost type (typeWithGenericTypeArgs) is generic, but if we're formatting a non-generic enclosing type, type.GetGenericTypeDefinition will bomb.

Exception when formatting name of System.Array+EmptyArray<T>

var type = Type.GetType("System.Array+EmptyArray`1");
Console.WriteLine(type.GetFormattedName());

fails with:

Unhandled Exception: System.InvalidOperationException: This operation is only valid on generic types.
   at System.RuntimeType.GetGenericTypeDefinition()
   at TypeNameFormatter.TypeName.AppendFormattedName(StringBuilder stringBuilder, Type type, TypeNameFormatOptions options, Type[] genericTypeArgs) in TypeName.cs:line 128
   at TypeNameFormatter.TypeName.AppendFormattedName(StringBuilder stringBuilder, Type type, TypeNameFormatOptions options, Type[] genericTypeArgs) in TypeName.cs:line 125
   at TypeNameFormatter.TypeName.AppendFormattedName(StringBuilder stringBuilder, Type type, TypeNameFormatOptions options) in TypeName.cs:line 49
   at TypeNameFormatter.TypeName.GetFormattedName(Type type, TypeNameFormatOptions options) in TypeName.cs:line 62
   at Program.Main()

It appears to be caused by an insufficiently accurate check added in be19c5f.

Consider adding nullable annotations

When the consuming project has nullable enabled, the way this file is added to the compilation (perhaps? maybe it would always be this way anyway?) causes a bunch of nullable warnings.

For "generated" code (this might fit that definition, since it's added as source), it's recommended (found this while generating code with roslyn source generators) that the file itself declares its nullable context at the top, with :

#nullable enable

(or the corresponding #nullable disable).

So it could either be entirely annotated and #nullable enable or just a line at the top with #nullable disable :)

API: Instead of `…Name` and `…FullName` methods, use a flags enum with formatting options

This could include more options than just whether to include the namespace or not; other possible options to consider:

  • whether or not to include type parameter names with open generic types (see #2)
  • whether or not to use C# keywords for primitive types (added in 868f39d)
  • whether or not to omit certain common namespace (such as System, System.Collections.Generic, etc.) (Let's not do this advanced functionality at this time.)

Publish as a NuGet package

Once the final API is fixed, this can be released on NuGet. Consider releasing this as a source-only package, too.

Add ExcludeFromCodeCoverage or make class partial

Since this project already has all unit tests for this class, it should be excluded from code coverage analysis on referencing projects.

This can be achieved by either annotating the TypeName class with [ExcludeFromCodeCoverage] or by making the class partial so the consuming project can declare the partial class and add the attribute.

Hope to add TypeNameFormatOptions.NoGeneric

/// <summary>
///   Specifies that an open generic type's parameter names should be omitted.
///   <example>
///     For example, the open generic type <see cref="IEquatable{T}"/> is formatted as <c>"IEquatable&lt;T&gt;"</c> by default.
///     When this flag is specified, it will be formatted as <c>"IEquatable&lt;&gt;"</c>.
///   </example>
/// </summary>
NoGeneric = 64

Line 253

if (IsSet(TypeNameFormatOptions.NoGeneric, options) == false && ownGenericTypeArgStartIndex < ownGenericTypeParamCount)
{
    //...
}

This is very effective for handling generic type merging. Thank you!
By the way, thank you for your contribution to Castle.Core.

Formatting options don't propagate inside anonymous types

On latest master, TypeNameFormatOptions do not propagage inside anonymous types:

using static TypeNameFormatOptions;

var obj = new { Name = "Name", Count = (int?)null };
Console.WriteLine(obj.GetType().GetFormattedName(NoKeywords | NoNullableQuestionMark));
// => {string Name, int? Count}

The output should've been {String Name, Nullable<Int32> Count}.

Support ValueTuple<...>

In 1.0.0-beta, typeof((short, uint)).GetFormattedName() == "ValueTuple<short, uint>". It would be nicer (more idiomatic C#) if this were to return "(short, uint)" (and similarly for ValueTuples of any arity).

Unit tests need some rearranging

  • While nice, not every possible combination of the various aspects (namespace, generic, nested, etc.) has to be tested. Perhaps lean towards testing each aspect only once, then trust that it will still work if combined with other aspects.

  • Perhaps bring unit test names closer in line with the tested types. Not as urgent once [Theory] is used. Now all that's needed is a good name for a whole group of test cases.

Should generic type parameters be rendered by default or not?

Currently, generic type parameter names are by default omitted from the formatted name unless specifically requested via TypeNameFormatOptions.GenericParameterNames. I've come to question whether it would be better to include parameter names by default, and turn the option into TypeNameFormatOptions.NoGenericParameterNames.

Some background:

class Some<T>
{
    public Some<T> Field;
}

var fieldType = typeof(Some<>).GetField("Field").FieldType;
Console.WriteLine(fieldType.GetFormattedName(TypeNameFormatOptions.Default));
// => Some<>

The rendered name Some<> is arguably wrong because T as used in the return type Some<T> is actually an argument, and the field type Some<T> is actually a constructed generic type, not a type definition. This isn't directly visible in the C# source, but it's apparent in IL:

.class private auto ansi beforefieldinit Some`1<T> extends [mscorlib]System.Object
{
    .field public class Some`1<!T> Field
    // ...
}

Note the difference between Some`1<T> (type name) vs. Some`1<!T> (field type).

Reflection cannot distinguish between these two things (see e.g. here), which means the library cannot figure out that it should never omit T in the field type.

Inverting the default formatting options (i.e. replacing GenericParameterNames with NoGenericParameterNames) would make it somewhat less probably that people stumble upon this inconsistency and think it's a bug in this library.

Debugger shouldn't step into formatting methods by default

Yet another aspect of source code distribution that isn't yet addressed is debugging behaviour. User code probably doesn't want to have the debugger step into stringBuilder.AppendFormattedName or type.GetFormattedName.

These extension methods should probably be decorates with a suitable [System.Diagnostics.Debugger...] custom attribute.

When using TypeNameFormatterInternal, configuration might cause diagnostics warnings/errors

Scenario:

  • Someone sets TypeNameFormatterInternal=true
  • Then via .editorconfig requires that default visibility is not specified (i.e. omit "internal" from class declarations, "private" from fields, etc.). This can be achieved with: dotnet_style_require_accessibility_modifiers = omit_if_default:error

Now the included TypeNameFormatter.cs will generate a build error (if set to error) due to:

#if TYPENAMEFORMATTER_INTERNAL
    internal
#else
    public
#endif

The tricky thing here is that the rule can be set either way, so removing the internal might cause build errors for someone with the opposite setting (that all visibility modifiers are required).

This could be eliminated by adding a SupressMessage to all declared types:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0040:Add accessibility modifiers")]

As a workaround, users can add the following to a .editorconfig:

[TypeNameFormatter.cs]
dotnet_style_require_accessibility_modifiers = none

`TypeName` and `TypeNameFormatOptions` types collide when NuGet package installed in several projects

  1. Create a new solution with two projects, say ClassLibrary and ConsoleApp.
  2. Add a reference to ClassLibrary in ConsoleApp.
  3. Install TypeNameFormatter in both projects.
  4. Try to use one of the extension methods in ConsoleApp and observe an error message such as:

CS0121: The call is ambiguous between the following methods or properties: 'TypeNameFormatter.TypeName.GetFormattedName(System.Type, TypeNameFormatter.TypeNameFormatOptions)' and 'TypeNameFormatter.TypeName.GetFormattedName(System.Type, TypeNameFormatter.TypeNameFormatOptions)'

Can't use the formatter inside razor templates

I'm using razor templating engine to generate source code. I found your library which seems to solve one issue I have. The problem is I can't use GetFormattedName extension inside razor templates (*.cshtml). I get:

error CS0122: 'TypeName.GetFormattedName(Type, TypeNameFormatOptions)' is inaccessible due to its protection level

This needs some investigation why it doesn't work.

[DebuggerStepThrough]
[EditorBrowsable(EditorBrowsableState.Never)]
#if TYPENAMEFORMATTER_INTERNAL
internal
#else
public
#endif
static class TypeName

Is TYPENAMEFORMATTER_INTERNAL really necessary? Why can't the class be just public all the time? It seems like it was defined during compilation and the class is internal.

Also see this issue for reference: https://stackoverflow.com/questions/42264992/using-internal-properties-in-razorengine

Open generic types shouldn't have parameter names included

For example:

var type = typeof(IEnumerable<>);
Console.WriteLine(type.GetFormattedName());
// => IEnumerable<T>

Since the type parameter name T isn't mentioned in C# code, it probably shouldn't end up in the formatted string, either.

Prettier formatting of anonymous types

In 1.0.0-beta, new {foo="foo", bar=42}.GetType().GetFormattedName() returns something like "<>f__AnonymousType5<string, int>". This is not wrong, as there is no C# naming convention for anonymous types, but I wonder if the library could return something prettier, e.g. "{string, int}"?

Format `System.IntPtr` and `System.UIntPtr` as C# 9 keywords `nint` and `nuint`?

This isn't as clear-cut as it may be, since System.IntPtr has traditionally been used in interop scenarios to represent unmanaged pointers outside of unsafe contexts. nint "feels" much closer to a regular integral type than it does to void*, so I'm not sure if we should throw those two types in the same basket as all the other primitive types.

Add Visual Basic as an alternate target language

This should be mostly straightforward. For example:

  • generic types: use (Of …) instead of <…>
  • array types: use (…) instead of []
  • by-ref types: use ByRef (or nothing) instead of ref (need to check)
  • pointer types: throw an exception?

NuGet package with DLL

We've adopted TypeNameFormatter now in our codebase (thanks!) - on version 1.0.0-beta for now but looking forward to the next version.

Unfortunately the road has been less than smooth because of the apparently poor support for source-only NuGet packages in both Visual Studio/ReSharper and Rider (as of the latest versions).

For Rider, see https://youtrack.jetbrains.com/issue/RIDER-16638.

For Visual Studio/ReSharper, I gather the problem is that ReSharper Build doesn't include the packaged sources correctly, if at all, causing the build to fail.

MSBuild seems to work perfectly.

I wonder please:

  • have you successfully integrated TypeNameFormatter.Sources (or other similarly-packaged libraries) into existing solutions?
  • would you consider providing a vanilla NuGet package containing a DLL targeting AnyCPU and (say) net47?

Regards
Matt

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.