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

improve perf of DisplayAttribute detection #1333

Merged
merged 3 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/Humanizer.Tests/Humanizer.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
<PackageReference Include="Verify.DiffPlex" Version="2.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<ProjectReference Include="..\Humanizer\Humanizer.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net48' ">
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.ComponentModel.DataAnnotations" Condition="'$(TargetFramework)' == 'net48' "/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Humanizer.Tests.Shared\**\*.cs">
Expand Down
68 changes: 24 additions & 44 deletions src/Humanizer/EnumHumanizeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Reflection;
using Humanizer.Configuration;
using System.ComponentModel.DataAnnotations;

namespace Humanizer
{
Expand All @@ -8,41 +9,34 @@ namespace Humanizer
/// </summary>
public static class EnumHumanizeExtensions
{
private const string DisplayAttributeTypeName = "System.ComponentModel.DataAnnotations.DisplayAttribute";
private const string DisplayAttributeGetDescriptionMethodName = "GetDescription";
private const string DisplayAttributeGetNameMethodName = "GetName";

private static readonly Func<PropertyInfo, bool> StringTypedProperty = p => p.PropertyType == typeof(string);

/// <summary>
/// Turns an enum member into a human readable string; e.g. AnonymousUser -> Anonymous user. It also honors DescriptionAttribute data annotation
/// </summary>
/// <param name="input">The enum member to be humanized</param>
public static string Humanize(this Enum input)
{
var enumType = input.GetType();
var enumTypeInfo = enumType.GetTypeInfo();
var type = input.GetType();

if (IsBitFieldEnum(enumTypeInfo) && !Enum.IsDefined(enumType, input))
if (IsBitFieldEnum(type) && !Enum.IsDefined(type, input))
{
return Enum.GetValues(enumType)
return Enum.GetValues(type)
.Cast<Enum>()
.Where(e => e.CompareTo(Convert.ChangeType(Enum.ToObject(enumType, 0), enumType)) != 0)
.Where(e => e.CompareTo(Convert.ChangeType(Enum.ToObject(type, 0), type)) != 0)
.Where(input.HasFlag)
.Select(e => e.Humanize())
.Humanize();
}

var caseName = input.ToString();
var memInfo = enumTypeInfo.GetDeclaredField(caseName);
var member = type.GetTypeInfo().GetDeclaredField(caseName);

if (memInfo != null)
if (member != null)
{
var customDescription = GetCustomDescription(memInfo);
var description = GetCustomDescription(member);

if (customDescription != null)
if (description != null)
{
return customDescription;
return description;
}
}

Expand All @@ -53,45 +47,31 @@ public static string Humanize(this Enum input)
/// Checks whether the given enum is to be used as a bit field type.
/// </summary>
/// <returns>True if the given enum is a bit field enum, false otherwise.</returns>
private static bool IsBitFieldEnum(TypeInfo typeInfo)
private static bool IsBitFieldEnum(Type type)
{
return typeInfo.GetCustomAttribute(typeof(FlagsAttribute)) != null;
return type.GetCustomAttribute(typeof(FlagsAttribute)) != null;
}

// I had to add this method because PCL doesn't have DescriptionAttribute & I didn't want two versions of the code & thus the reflection
private static string GetCustomDescription(MemberInfo memberInfo)
{
var attrs = memberInfo.GetCustomAttributes(true);

foreach (var attr in attrs)
var displayAttribute = memberInfo.GetCustomAttribute<DisplayAttribute>();
if (displayAttribute != null)
{
var attrType = attr.GetType();
if (attrType.FullName == DisplayAttributeTypeName)
var description = displayAttribute.GetDescription();
if (description != null)
{
var methodGetDescription = attrType.GetRuntimeMethod(DisplayAttributeGetDescriptionMethodName, new Type[0]);
if (methodGetDescription != null)
{
var executedMethod = methodGetDescription.Invoke(attr, new object[0]);
if (executedMethod != null)
{
return executedMethod.ToString();
}
}
var methodGetName = attrType.GetRuntimeMethod(DisplayAttributeGetNameMethodName, new Type[0]);
if (methodGetName != null)
{
var executedMethod = methodGetName.Invoke(attr, new object[0]);
if (executedMethod != null)
{
return executedMethod.ToString();
}
}
return null;
return description;
}

return displayAttribute.GetName();
}

foreach (var attr in memberInfo.GetCustomAttributes())
{
var attrType = attr.GetType();
var descriptionProperty =
attrType.GetRuntimeProperties()
.Where(StringTypedProperty)
.Where(p => p.PropertyType == typeof(string))
.FirstOrDefault(Configurator.EnumDescriptionPropertyLocator);
if (descriptionProperty != null)
{
Expand Down
3 changes: 2 additions & 1 deletion src/Humanizer/Humanizer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
</PropertyGroup>
<ItemGroup>
<Using Remove="System.Net.Http" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.3.0" Condition="'$(TargetFramework)' == 'netstandard2.0' " />
<None Update="FluentDate\InDate.Months.tt">
<LastGenOutput>InDate.Months.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
Expand Down Expand Up @@ -46,5 +47,5 @@
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
</ItemGroup>
</Project>
Loading