Last active
January 25, 2022 13:49
-
-
Save in-async/9cc74018551db143deaa0f36f7148cd4 to your computer and use it in GitHub Desktop.
Optional<T> 型
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Globalization; | |
using System.Reflection; | |
namespace Inasync { | |
//[TypeDescriptionProvider(typeof(OptionalTypeDescriptionProvider))] | |
[TypeConverter(typeof(OptionalTypeConverter))] | |
[Newtonsoft.Json.JsonConverter(typeof(OptionalNSJsonConverter))] | |
[System.Text.Json.Serialization.JsonConverter(typeof(OptionalJsonConverter))] | |
public readonly struct Optional<T> : IEquatable<Optional<T>>, IOptional { | |
private readonly T _value; | |
private readonly bool _dirty; | |
private Optional(T value) { | |
_value = value; | |
_dirty = true; | |
} | |
public T Value => _value; | |
public static implicit operator T(Optional<T> optional) => optional._value; | |
public static implicit operator Optional<T>(T value) => new(value); | |
public override string? ToString() => _dirty && _value is { } ? _value.ToString() : ""; | |
object? IOptional.Value => _value; | |
bool IOptional.IsDirty => _dirty; | |
#region IEquatable<Optional<T>> implements | |
/// <remarks>Auto gen</remarks> | |
public override bool Equals(object? obj) { | |
return obj is Optional<T> optional && Equals(optional); | |
} | |
/// <remarks>Auto gen</remarks> | |
public bool Equals(Optional<T> other) { | |
return _dirty == other._dirty | |
&& EqualityComparer<T>.Default.Equals(_value, other._value) | |
; | |
} | |
/// <remarks>Auto gen</remarks> | |
public override int GetHashCode() { | |
return HashCode.Combine(_value, _dirty); | |
} | |
/// <remarks>Auto gen</remarks> | |
public static bool operator ==(Optional<T> left, Optional<T> right) { | |
return left.Equals(right); | |
} | |
/// <remarks>Auto gen</remarks> | |
public static bool operator !=(Optional<T> left, Optional<T> right) { | |
return !(left == right); | |
} | |
#endregion IEquatable<Optional<T>> implements | |
} | |
public interface IOptional { | |
public object? Value { get; } | |
public bool IsDirty { get; } | |
} | |
internal sealed class OptionalTypeConverter : TypeConverter { | |
private readonly Type _optionalType; | |
private readonly Type _valueType; | |
private readonly TypeConverter _valueConverter; | |
/// <remarks> | |
/// see <![CDATA[https://stackoverflow.com/questions/14823669/cannot-create-a-typeconverter-for-a-generic-type/14980794#14980794]]> | |
/// </remarks> | |
public OptionalTypeConverter(Type type) { | |
_optionalType = type; | |
_valueType = type.GenericTypeArguments[0]; | |
_valueConverter = TypeDescriptor.GetConverter(_valueType); | |
} | |
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { | |
return _valueConverter.CanConvertFrom(context, sourceType); | |
} | |
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo culture, object? value) { | |
return Activator.CreateInstance( | |
_optionalType | |
, BindingFlags.NonPublic | BindingFlags.Instance | |
, binder: null | |
, args: new[] { _valueConverter.ConvertFrom(context, culture, value) } | |
, culture: null | |
)!; | |
} | |
public override bool CanConvertTo(ITypeDescriptorContext? context, Type destinationType) { | |
return _valueConverter.CanConvertTo(context, destinationType); | |
} | |
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo culture, object? value, Type destinationType) { | |
var optional = (IOptional?)value; | |
return _valueConverter.ConvertTo(context, culture, optional?.Value, destinationType); | |
} | |
} | |
} | |
namespace Inasync { | |
using Newtonsoft.Json; | |
internal sealed class OptionalNSJsonConverter : JsonConverter { | |
public override bool CanConvert(Type objectType) { | |
throw new NotSupportedException(); | |
} | |
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { | |
if (objectType.GetGenericTypeDefinition() == typeof(Optional<>)) { | |
object? value = serializer.Deserialize(reader, objectType: objectType.GenericTypeArguments[0]); | |
return Activator.CreateInstance( | |
objectType | |
, BindingFlags.NonPublic | BindingFlags.Instance | |
, binder: null | |
, args: new[] { value } | |
, culture: null | |
); | |
} | |
throw new NotSupportedException(); | |
} | |
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { | |
if (!(value is IOptional optional)) { return; } | |
if (optional.IsDirty) { | |
serializer.Serialize(writer, optional.Value); | |
} | |
else { | |
writer.WriteNull(); | |
} | |
} | |
} | |
} | |
namespace Inasync { | |
using System.Text.Json; | |
using System.Text.Json.Serialization; | |
internal sealed class OptionalJsonConverter : JsonConverterFactory { | |
public override bool CanConvert(Type typeToConvert) { | |
return true; | |
} | |
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) { | |
Type valueType = typeToConvert.GenericTypeArguments[0]; | |
return (JsonConverter?)Activator.CreateInstance( | |
typeof(OptionalJsonConverter<>).MakeGenericType(valueType) | |
, BindingFlags.Instance | BindingFlags.Public | |
, binder: null | |
, args: new object[] { options } | |
, culture: null | |
); | |
} | |
} | |
internal sealed class OptionalJsonConverter<T> : JsonConverter<Optional<T>> { | |
private readonly JsonConverter<T> _valueConverter; | |
public OptionalJsonConverter(JsonSerializerOptions options) { | |
_valueConverter = (JsonConverter<T>)options.GetConverter(typeof(T)); | |
} | |
public override Optional<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { | |
return _valueConverter.Read(ref reader, typeof(T), options) ?? default(Optional<T>); | |
} | |
public override void Write(Utf8JsonWriter writer, Optional<T> value, JsonSerializerOptions options) { | |
if (value == default) { | |
writer.WriteNullValue(); | |
} | |
else { | |
_valueConverter.Write(writer, (T)value, options); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Inasync; | |
using Newtonsoft.Json; | |
namespace Samples { | |
internal class Program { | |
private static void Main(string[] args) { | |
Account entity = new(1, DateTime.UtcNow); | |
string json = JsonConvert.SerializeObject(entity, settings: new JsonSerializerSettings { | |
DefaultValueHandling = DefaultValueHandling.Ignore, | |
}); | |
Console.WriteLine(json); | |
// {"AccountId":1,"CreatedAt":"2022-01-25T13:39:28.8380321Z"} | |
Console.WriteLine(JsonConvert.DeserializeObject<Account>(json)); | |
// Account { AccountId = 1, CreatedAt = 2022/01/25 13:39:28, Name = } | |
} | |
} | |
internal record Account( | |
int AccountId | |
, DateTime CreatedAt | |
) { | |
public Optional<string> Name { get; set; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment