Skip to content

Commit

Permalink
improve perf of DisplayAttribute detection (#1333)
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonCropp committed Feb 13, 2024
1 parent 810c104 commit efa1d7e
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 48 deletions.
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>

0 comments on commit efa1d7e

Please sign in to comment.