Skip to content

Instantly share code, notes, and snippets.

@in-async
Last active January 25, 2022 13:49
Show Gist options
  • Save in-async/9cc74018551db143deaa0f36f7148cd4 to your computer and use it in GitHub Desktop.
Save in-async/9cc74018551db143deaa0f36f7148cd4 to your computer and use it in GitHub Desktop.
Optional<T> 型
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);
}
}
}
}
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