ZK_Framework/Assets/Plugins/MessagePack/Formatters/EnumAsStringFormatter`1.cs

129 lines
5.2 KiB
C#

// Copyright (c) All contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
namespace MessagePack.Formatters
{
// Note:This implementation is 'not' fastest, should more improve.
public sealed class EnumAsStringFormatter<T> : IMessagePackFormatter<T>
{
private readonly IReadOnlyDictionary<string, T> nameValueMapping;
private readonly IReadOnlyDictionary<T, string> valueNameMapping;
private readonly IReadOnlyDictionary<string, string> clrToSerializationName;
private readonly IReadOnlyDictionary<string, string> serializationToClrName;
private readonly bool enumMemberOverridesPresent;
private readonly bool isFlags;
public EnumAsStringFormatter()
{
this.isFlags = typeof(T).GetCustomAttribute<FlagsAttribute>() is object;
var fields = typeof(T).GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static);
var nameValueMapping = new Dictionary<string, T>(fields.Length);
var valueNameMapping = new Dictionary<T, string>();
Dictionary<string, string> clrToSerializationName = null;
Dictionary<string, string> serializationToClrName = null;
foreach (FieldInfo enumValueMember in fields)
{
string name = enumValueMember.Name;
T value = (T)enumValueMember.GetValue(null);
// Consider the case where the serialized form of the enum value is overridden via an attribute.
var attribute = enumValueMember.GetCustomAttribute<EnumMemberAttribute>();
if (attribute?.IsValueSetExplicitly ?? false)
{
clrToSerializationName = clrToSerializationName ?? new Dictionary<string, string>();
serializationToClrName = serializationToClrName ?? new Dictionary<string, string>();
clrToSerializationName.Add(name, attribute.Value);
serializationToClrName.Add(attribute.Value, name);
name = attribute.Value;
this.enumMemberOverridesPresent = true;
}
nameValueMapping[name] = value;
valueNameMapping[value] = name;
}
this.nameValueMapping = nameValueMapping;
this.valueNameMapping = valueNameMapping;
this.clrToSerializationName = clrToSerializationName;
this.serializationToClrName = serializationToClrName;
}
public void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options)
{
// Enum.ToString() is slow, so avoid it when we can.
if (!this.valueNameMapping.TryGetValue(value, out string valueString))
{
// fallback for flags, values with no name, etc
valueString = this.GetSerializedNames(value.ToString());
}
writer.Write(valueString);
}
public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
string name = reader.ReadString();
// Avoid Enum.Parse when we can because it is too slow.
if (!this.nameValueMapping.TryGetValue(name, out T value))
{
value = (T)Enum.Parse(typeof(T), this.GetClrNames(name));
}
return value;
}
private string GetClrNames(string serializedNames)
{
if (this.enumMemberOverridesPresent && this.isFlags && serializedNames.IndexOf(", ", StringComparison.Ordinal) >= 0)
{
return Translate(serializedNames, this.serializationToClrName);
}
// We don't need to consider the trivial case of no commas because our caller would have found that in the lookup table and not called us.
return serializedNames;
}
private string GetSerializedNames(string clrNames)
{
if (this.enumMemberOverridesPresent && this.isFlags && clrNames.IndexOf(", ", StringComparison.Ordinal) >= 0)
{
return Translate(clrNames, this.clrToSerializationName);
}
// We don't need to consider the trivial case of no commas because our caller would have found that in the lookup table and not called us.
return clrNames;
}
private static string Translate(string items, IReadOnlyDictionary<string, string> mapping)
{
string[] elements = items.Split(',');
for (int i = 0; i < elements.Length; i++)
{
// Trim the leading space if there is one (due to the delimiter being ", ").
if (i > 0 && elements[i].Length > 0 && elements[i][0] == ' ')
{
elements[i] = elements[i].Substring(1);
}
if (mapping.TryGetValue(elements[i], out string substituteValue))
{
elements[i] = substituteValue;
}
}
return string.Join(", ", elements);
}
}
}