Skip to content

Commit 09ff1f8

Browse files
russcamawelburn
authored and
awelburn
committed
Faster enum -> string resolution (elastic#2418)
Specific enum GetStringValue extension methods for known enums Caching of generic method of resolving string values for enums based on enum type. Renamed KnownEnums.Resolve to GetStringValue and made an extension method. Added GetStringValue extension method for HttpMethod - this is a hot path, called on every request. Fixed casing issue in HttpConnection for setting ContentLength Closes elastic#2400 Conflicts: src/Elasticsearch.Net/Domain/Enums.Generated.cs
1 parent 746e21d commit 09ff1f8

File tree

23 files changed

+264
-180
lines changed

23 files changed

+264
-180
lines changed
Lines changed: 115 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
@using System
12
@using System.Collections.Generic
23
@using System.Linq
34
@using ApiGenerator.Domain
45
@using ApiGenerator
5-
66
@functions {
77
private const string RawSize = "Raw";
88
private const string SizeEnum = "Size";
@@ -14,75 +14,149 @@
1414
}
1515
private string CreateCase(string e, string o)
1616
{
17-
var enumValue = (e == SizeEnum && o == string.Empty) ? RawSize : o.ToPascalCase(true);
17+
var enumValue = GetEnumValue(e, o);
1818
return string.Format("case {0}.{1}: return \"{2}\";", e, enumValue, o);
1919
}
2020
private bool IsFlag(string name)
2121
{
22-
return (name.EndsWith("Metric")) || name.EndsWith("Feature");
22+
return name.EndsWith("Metric") || name.EndsWith("Feature");
23+
}
24+
25+
private string CreateEnumKeyValue(string enumName, string value, int index)
26+
{
27+
var enumValue = GetEnumValue(enumName, value);
28+
return string.Format("{3}{{ {0}.{1}, \"{2}\" }},", enumName, enumValue, value, index == 0 ? "\t\t\t\t" : string.Empty);
29+
}
30+
31+
private string GetEnumValue(string enumName, string value)
32+
{
33+
return enumName == SizeEnum && value == string.Empty
34+
? RawSize
35+
: value.ToPascalCase(true);
2336
}
2437
}
2538
using System;
2639
using System.Collections.Generic;
40+
using System.Collections.Concurrent;
2741
using System.Linq;
2842
using System.Text;
43+
using System.Reflection;
2944
using System.Runtime.Serialization;
3045

3146
///This file contains all the typed enums that the client rest api spec exposes.
3247
///This file is automatically generated from https://github.com/elastic/elasticsearch/tree/@Model.Commit/rest-api-spec
3348
///Generated of commit @Model.Commit
3449
namespace Elasticsearch.Net
3550
{
36-
@foreach (EnumDescription e in Model.EnumsInTheSpec)
37-
{
38-
var isFlag = IsFlag(e.Name);
51+
@foreach (EnumDescription e in Model.EnumsInTheSpec)
52+
{
53+
var isFlag = IsFlag(e.Name);
3954
<text>
40-
@(isFlag ? "[Flags]" : string.Empty)public enum @e.Name
55+
@(isFlag ? "[Flags]" : string.Empty)public enum @e.Name
4156
{
42-
@Raw(string.Join(",\r\n\t\t", e.Options.OrderBy(s=>s == "_all" ? 1 : 0).Select((s, i) => CreateEnum(e.Name, s, isFlag ? (int?)i : null ))))
43-
}
44-
</text>
45-
}
57+
@Raw(string.Join(",\r\n\t\t", e.Options.OrderBy(s => s == "_all" ? 1 : 0).Select((s, i) => CreateEnum(e.Name, s, isFlag ? (int?)i : null))))
58+
}</text>
59+
}
4660

4761
public static class KnownEnums
62+
{
63+
private class EnumDictionary : @(Raw("Dictionary<Enum, string>"))
64+
{
65+
public EnumDictionary(int capacity) : base(capacity) {}
66+
public @(Raw("Func<Enum, string>")) Resolver { get; set; }
67+
}
68+
69+
@foreach (EnumDescription e in Model.EnumsInTheSpec)
4870
{
49-
public static string UnknownEnum { get; } = "_UNKNOWN_ENUM_";
50-
public static string Resolve(Enum e)
71+
var isFlag = IsFlag(e.Name);
72+
<text>
73+
public static string GetStringValue(this @(e.Name) enumValue)
74+
{
75+
</text>
76+
if (isFlag)
77+
{
78+
var allOption = e.Options.FirstOrDefault(o => o == "_all");
79+
if (allOption != null)
80+
{
81+
<text>if ((enumValue & @(e.Name).All) != 0) return "_all";</text>
82+
}
83+
<text>var list = new @(Raw("List<string>()"));</text>
84+
foreach (var option in e.Options.Where(o => o != "_all"))
85+
{
86+
<text>if ((enumValue & @(e.Name).@(GetEnumValue(e.Name, option))) != 0) list.Add("@(option)");</text>
87+
}
88+
<text>return string.Join(",", list);
89+
}</text>
90+
}
91+
else
92+
{
93+
<text>switch (enumValue)
94+
{
95+
@Raw(string.Join("\r\n\t\t\t\t", e.Options.Select(o => CreateCase(e.Name, o))))
96+
}
97+
throw new ArgumentException($"'{enumValue.ToString()}' is not a valid value for enum '@(e.Name)'");
98+
}</text>
99+
}
100+
}
101+
102+
private static readonly @(Raw("ConcurrentDictionary<Type, Func<Enum, string>>")) EnumStringResolvers =
103+
new @(Raw("ConcurrentDictionary<Type, Func<Enum, string>>"))();
104+
105+
static KnownEnums()
106+
{
107+
@foreach (EnumDescription e in Model.EnumsInTheSpec)
108+
{
109+
<text>EnumStringResolvers.TryAdd(typeof(@(e.Name)), (e) => GetStringValue((@(e.Name))e));</text>
110+
}
111+
}
112+
113+
public static string GetStringValue(this Enum e)
114+
{
115+
var type = e.GetType();
116+
var resolver = EnumStringResolvers.GetOrAdd(type, GetEnumStringResolver);
117+
return resolver(e);
118+
}
119+
120+
private static @Raw("Func<Enum, string>") GetEnumStringResolver(Type type)
51121
{
52-
@foreach (EnumDescription e in Model.EnumsInTheSpec)
122+
var values = Enum.GetValues(type);
123+
var dictionary = new EnumDictionary(values.Length);
124+
125+
for (int index = 0; index < values.Length; index++)
53126
{
54-
var isFlag = IsFlag(e.Name);
55-
<text>if (e is @e.Name)
56-
{ </text>
57-
if (isFlag)
127+
var value = values.GetValue(index);
128+
#if DOTNETCORE
129+
var info = type.GetTypeInfo().GetDeclaredField(value.ToString());
130+
#else
131+
var info = type.GetField(value.ToString());
132+
#endif
133+
var da = (EnumMemberAttribute[])info.GetCustomAttributes(typeof(EnumMemberAttribute), false);
134+
var stringValue = da.Length > 0 ? da[0].Value : Enum.GetName(type, value);
135+
dictionary.Add((Enum)value, stringValue);
136+
}
137+
138+
#if DOTNETCORE
139+
var isFlag = type.GetTypeInfo().GetCustomAttributes(typeof(FlagsAttribute), false).Any();
140+
#else
141+
var isFlag = type.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0;
142+
#endif
143+
144+
return (e) =>
58145
{
59-
<text>var list = new @(Raw("List<string>()"));</text>
60-
foreach(var option in e.Options.OrderBy(s=>s == "_all" ? 1 : 0))
146+
if (isFlag)
61147
{
62-
if (option != "_all")
148+
var list = new @(Raw("List<string>()"));
149+
foreach(var kv in dictionary)
63150
{
64-
<text>if (e.HasFlag(@(e.Name).@(option.ToPascalCase(true)))) list.Add("@(option)");</text>
65-
}
66-
else
67-
{
68-
<text>if (e.HasFlag(@(e.Name).@(option.ToPascalCase(true)))) return "@(option)";</text>
151+
if (e.HasFlag(kv.Key)) list.Add(kv.Value);
69152
}
153+
return string.Join(",", list);
70154
}
71-
<text>return string.Join(",", list);</text>
72-
}
73-
else
74-
{
75-
<text>switch((@e.Name)e)
155+
else
76156
{
77-
@Raw(string.Join("\r\n\t\t\t\t\t", e.Options.Select(o =>CreateCase(e.Name,o))))
78-
}</text>
79-
}
80-
<text>
81-
}
82-
</text>
83-
}
84-
return UnknownEnum;
157+
return dictionary[e];
158+
}
159+
};
85160
}
86161
}
87-
}
88-
162+
}

src/Elasticsearch.Net/Connection/HttpConnection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ protected virtual HttpWebRequest CreateWebRequest(RequestData requestData)
6666
//see: https://github.com/elasticsearch/elasticsearch-net/issues/562
6767
var m = requestData.Method.GetStringValue();
6868
request.Method = m;
69-
if (m != "head" && m != "get" && (requestData.PostData == null))
69+
if (m != "HEAD" && m != "GET" && (requestData.PostData == null))
7070
request.ContentLength = 0;
7171

7272
return request;

src/Elasticsearch.Net/Connection/HttpMethod.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ public enum HttpMethod
1717
[EnumMember(Value = "HEAD")]
1818
HEAD
1919
}
20-
}
20+
}

src/Elasticsearch.Net/Elasticsearch.Net.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
<Compile Include="Exceptions\ElasticsearchClientException.cs" />
8585
<Compile Include="Exceptions\ResolveException.cs" />
8686
<Compile Include="Exceptions\UnexpectedElasticsearchClientException.cs" />
87+
<Compile Include="Extensions\EnumExtensions.cs" />
8788
<Compile Include="Extensions\Extensions.cs" />
8889
<Compile Include="Extensions\Fluent.cs" />
8990
<Compile Include="CrossPlatform\FormattableString.cs" />
@@ -129,4 +130,4 @@
129130
<Import Project="..\outputpath.props" />
130131
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
131132
<Import Project="..\..\.paket\paket.targets" />
132-
</Project>
133+
</Project>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Elasticsearch.Net
8+
{
9+
public static class EnumExtensions
10+
{
11+
public static string GetStringValue(this HttpMethod enumValue)
12+
{
13+
switch (enumValue)
14+
{
15+
case HttpMethod.GET: return "GET";
16+
case HttpMethod.POST: return "POST";
17+
case HttpMethod.PUT: return "PUT";
18+
case HttpMethod.DELETE: return "DELETE";
19+
case HttpMethod.HEAD: return "HEAD";
20+
default:
21+
throw new ArgumentOutOfRangeException(nameof(enumValue), enumValue, null);
22+
}
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)