using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Security.Permissions;
namespace NitroxModel.DataStructures.Util
{
///
/// Used to give context on whether the wrapped value is nullable and to improve error logging.
///
///
/// Used some hacks to circumvent C#' lack of reverse type inference (usually need to specify the type when returning a
/// value using a generic method).
/// Code from https://tyrrrz.me/blog/return-type-inference
///
///
[Serializable]
[DataContract]
public struct Optional : ISerializable, IEquatable> where T : class
{
private delegate bool HasValueDelegate(T value);
///
/// List of condition checks for current type (due to being a static on generic class).
///
private static List> valueChecks;
///
/// Has value check that can be replaced and defaults to generating a value check for current based on
/// global filter conditions that were set.
///
private static HasValueDelegate valueChecksForT = value =>
{
// Generate new HasValue check based on global filters for types.
Type type = typeof(T);
bool isObj = type == typeof(object);
foreach (KeyValuePair> filter in Optional.ValueConditions)
{
if (isObj || filter.Key.IsAssignableFrom(type))
{
// Only create the list in memory when required.
valueChecks ??= new List>();
// Exclude check for Optional if the type doesn't match the type of the filter (because it'll always be null for `o as T`)
valueChecks.Add(isObj ? o => !filter.Key.IsInstanceOfType(o) || filter.Value(o) : filter.Value);
}
}
// Update check to just check has values directly for future calls (this is an optimization).
if (valueChecks != null)
{
valueChecksForT = val =>
{
if (ReferenceEquals(val, null))
{
return false;
}
foreach (Func check in valueChecks)
{
if (!check(val))
{
return false;
}
}
return true;
};
}
else
{
valueChecksForT = val => !ReferenceEquals(val, null);
}
// Give initial result based on the updated check delegate
return valueChecksForT(value);
};
[DataMember(Order = 1)]
public T Value { get; private set; }
public bool HasValue => valueChecksForT(Value);
private Optional(T value)
{
Value = value;
}
public T OrElse(T elseValue)
{
return HasValue ? Value : elseValue;
}
public Optional OrElse(Func elseValue) => HasValue ? Value : elseValue();
public T OrNull() => HasValue ? Value : null;
internal static Optional Of(T value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value), $"Tried to set null on {typeof(Optional)}");
}
return new Optional(value);
}
internal static Optional OfNullable(T value)
{
return !valueChecksForT(value) ? Optional.Empty : new Optional(value);
}
public override string ToString()
{
string str = Value != null ? Value.ToString() : "Nothing";
return $"Optional Contains: {str}";
}
private Optional(SerializationInfo info, StreamingContext context)
{
Value = (T)info.GetValue("value", typeof(T));
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("value", Value);
}
#pragma warning disable CS0618 // OptionalEmpty is only allowed to be used internally
public static implicit operator Optional(OptionalEmpty none)
{
return new Optional();
}
#pragma warning restore CS0618
public static implicit operator Optional?(T obj)
{
if (obj == null)
{
return null;
}
return new Optional(obj);
}
public static implicit operator Optional(T obj)
{
return Optional.Of(obj);
}
public static explicit operator T(Optional value)
{
return value.Value;
}
public bool Equals(Optional other)
{
return EqualityComparer.Default.Equals(Value, other.Value);
}
public override bool Equals(object obj)
{
return obj is Optional other && Equals(other);
}
public override int GetHashCode()
{
return EqualityComparer.Default.GetHashCode(Value);
}
public static bool operator ==(Optional left, Optional right)
{
return left.Equals(right);
}
public static bool operator !=(Optional left, Optional right)
{
return !left.Equals(right);
}
}
[Obsolete("Use Optional.Empty instead. This struct is required to trick the compiler for the lack of reverse type inference.")]
public struct OptionalEmpty
{
public OptionalEmpty()
{
}
}
public static class Optional
{
internal static readonly Dictionary> ValueConditions = new();
#pragma warning disable CS0618 // OptionalEmpty is only allowed to be used internally
public static OptionalEmpty Empty { get; } = new();
#pragma warning restore CS0618
public static Optional Of(T value) where T : class => Optional.Of(value);
public static Optional OfNullable(T value) where T : class => Optional.OfNullable(value);
///
/// Adds a condition to the optional of the given type that is checked whenever is
/// checked.
///
/// Condition to add to the check.
///
/// Type that should have the extra condition. The given type will also apply to more specific types than
/// itself.
///
public static void ApplyHasValueCondition(Func hasValueCondition) where T : class
{
// Add to global so that the Optional can lazily evaluate which conditions it should add to its checks based on its type.
ValueConditions.Add(typeof(T), o => hasValueCondition(o as T));
}
}
public sealed class OptionalNullException : Exception
{
public OptionalNullException() : base($"Optional <{nameof(T)}> is null!")
{
}
public OptionalNullException(string message) : base($"Optional <{nameof(T)}> is null:\n\t{message}")
{
}
}
[Serializable]
public sealed class OptionalEmptyException : Exception
{
public OptionalEmptyException() : base($"Optional <{nameof(T)}> is empty.")
{
}
public OptionalEmptyException(string message) : base($"Optional <{nameof(T)}> is empty:\n\t{message}")
{
}
}
}