Skip to content

Instantly share code, notes, and snippets.

@otac0n
Created April 1, 2025 02:07
Show Gist options
  • Save otac0n/a1fe334b1eec8c5618833d728a237a15 to your computer and use it in GitHub Desktop.
Save otac0n/a1fe334b1eec8c5618833d728a237a15 to your computer and use it in GitHub Desktop.
A pair of C# functions to format a dotnet type as a C# syntax string (given many ways to minimize type name, if desired).
// usage:
// typeof(System.Collections.Generic.List<System.Double>).ToFullyQualifiedName()
// output:
// System.Collections.Generic.List<System.Double>
//
// usage:
// typeof(System.Collections.Generic.List<System.Double>).ToCSharpString(
// symbolDisplayFormat: new SymbolDisplayFormat(
// typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
// genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
// miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes))
// output:
// List<double>
public static string ToCSharpString(this Type type, string[] usingNamespaces = null, Assembly[] usingAssemblies = null, SymbolDisplayFormat symbolDisplayFormat = null)
{
var compilationUnit = SyntaxFactory.CompilationUnit();
if (usingNamespaces != null)
{
compilationUnit = compilationUnit.AddUsings(
Array.ConvertAll(usingNamespaces, n => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(n))));
}
else
{
compilationUnit = compilationUnit.AddUsings(
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System")));
}
MetadataReference[] metadataReferences;
if (usingAssemblies != null)
{
metadataReferences = Array.ConvertAll(usingAssemblies, u => MetadataReference.CreateFromFile(u.Location));
}
else
{
metadataReferences = new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(type.Assembly.Location)
};
}
var typeName = SyntaxFactory.ParseTypeName(type.ToFullyQualifiedName());
var field = SyntaxFactory.FieldDeclaration(
SyntaxFactory.VariableDeclaration(typeName).WithVariables(
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
SyntaxFactory.VariableDeclarator(
SyntaxFactory.Identifier("field")))));
compilationUnit = compilationUnit.AddMembers(
SyntaxFactory.ClassDeclaration("MyClass").AddMembers(
field))
.NormalizeWhitespace();
var tree = compilationUnit.SyntaxTree;
var compilation = CSharpCompilation.Create("MyAssembly", new[] { tree }, metadataReferences);
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var typeSymbol = semanticModel.GetTypeInfo(compilationUnit
.DescendantNodes().OfType<ClassDeclarationSyntax>().Single()
.Members.OfType<FieldDeclarationSyntax>().Single()
.Declaration.Type);
return typeSymbol.Type.ToDisplayString(symbolDisplayFormat ?? new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes));
}
public static string ToFullyQualifiedName(this Type type)
{
switch (type)
{
case { IsGenericParameter: true }: return type.Name;
case { IsArray: true }: return type.GetElementType().ToFullyQualifiedName() + "[]";
case { IsPointer: true }: return type.GetElementType().ToFullyQualifiedName() + "*";
case { IsByRef: true }: return type.GetElementType().ToFullyQualifiedName() + "&";
case { IsGenericType: false }: return string.IsNullOrEmpty(type.FullName) ? type.Name : type.FullName.Replace('+', '.');
default:
var fullName = type.GetGenericTypeDefinition().FullName;
var backTickIndex = fullName.IndexOf('`');
if (backTickIndex > 0)
{
fullName = fullName.Substring(0, backTickIndex);
}
return fullName.Replace('+', '.') + "<" + string.Join(", ", type.GetGenericArguments().Select(ToFullyQualifiedName)) + ">";
};
}
@jzabroski
Copy link

If it's an Exception, I'd like to see it use https://github.com/benaadams/Ben.Demystifier?tab=readme-ov-file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment