Elvis (?.) Метод расширения в С# 5.0
Можно ли создать некоторый метод расширения в С# 5.0, чтобы дать те же результаты, что и оператор С# 6.0 Elvis (?.)?
Например:
//C# 6.0 way
var g1 = parent?.child?.child?.child;
if (g1 != null) // TODO
//C# 5.0 way
var g1 = parent.elvisExtension().child.elvisExtension().child.elvisExtension().child;
if (g1 != null) // TODO
Ответы
Ответ 1
Возможно, можно использовать ту же методологию, что и насмешка (вместо возврата parent
она вернет "макет" родителя, который либо вернет null, либо значение объектов). Однако это немного усложняется.
Это довольно прямолинейно и обеспечивает базовую функциональность:
public static class Helper
{
public static TReturnType Elvis<TOnType, TReturnType>(this TOnType onObj, Func<TOnType, TReturnType> selector)
where TReturnType : class
{
if (onObj == null)
return null;
return selector(onObj);
}
}
Тестирование:
var person = new Person { Parent = new Person { Parent = new Person() } };
var result = person.Elvis(p => p.Parent).Elvis(p => p.Parent);
Правильно получает объект Person
.
person = new Person();
result = person.Elvis(p => p.Parent).Elvis(p => p.Parent);
Возвращает null.
Однако это работает только для типов с нулевым значением. К сожалению, вы не можете создать перегруз для where TReturnType : struct
, нам нужен новый метод для его обработки.
Итак, для типов с непустым значением нам нужно следующее:
public static TReturnType? Elviss<TOnType, TReturnType>(this TOnType onObj, Func<TOnType, TReturnType> selector)
where TReturnType : struct
{
if (onObj == null)
return default(Nullable<TReturnType>);
return selector(onObj);
}
И тестируем его:
var result = person.Elvis(p => p.Parent).Elviss(p => p.Id);
Ответ 2
Да; он является монадическим оператором карты и называется Select() в LINQ. Когда вы добавите другой связанный метод расширения LINQ, монадический оператор сглаживания SelectMany(), вы можете начать использовать синтаксис понимания LINQ, чтобы заставить Элвиса размахивать бедрами.
using System;
namespace ConsoleApplication1 {
public class Program {
public static void Main() {
var s1 = "String1";
var s2 = "String2";
var s3 = (string)null;
Console.WriteLine((from u in s1
from v in s2
select u.Replace("1", "45") + " "
+ v.Replace("2", "33")) ?? "Nothing");
Console.WriteLine((from u in s1
from v in s3
select u.Replace("1", "45") + " "
+ v.Replace("2", "33")) ?? "Nothing");
Console.ReadLine();
}
}
public static class Extensions {
public static TResult Select<TValue, TResult>(this
TValue @this,
Func<TValue, TResult> projector
) where TValue : class where TResult : class {
return @this==null ? null : projector(@this);
}
public static TResult SelectMany<TValue, T, TResult>(this
TValue @this,
Func<TValue, T> selector,
Func<TValue, T, TResult> resultSelector
) where TValue : class where TResult : class where T : class {
return @this==null ? null : selector(@this).Select(e => resultSelector(@this, e)); ;
}
}
}
производит как выход:
String45 String33
Nothing
Следующий шаг - обернуть все это в структуру, чтобы не разоблачить голую ссылку; добавить кодовые контракты, чтобы рекламировать клиентам результаты, не связанные с ошибкой; и сохраните Value Equality, основанное на базовом типе, и один облепил ссылки на обнаженные объекты в том, что выглядит замечательно как недействительный ссылочный тип:
using System;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
namespace PGSolutions.Utilities.Monads {
using static Contract;
/// <summary>An immutable value-type MaybeX{T} monad.</summary>
/// <typeparam name="TValue">The base type, which can be either a class or struct type,
/// and will have the Equality definition track the default for the base-type:
/// Value-equality for structs and string, reference equality for other classes.
/// </typeparam>
/// <remarks
/// >Being a value-type reduces memory pressure on <see cref="System.GC"/>.
///
/// Equality tracks the base type (struct or class), with the further proviseo
/// that two instances can only be equal when <see cref="HasValue"/> is true
/// for both instances.
/// </remarks>
public struct MaybeX<T> : IEquatable<MaybeX<T>> where T:class {
/// <summary>The Invalid Data value.</summary>
[SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
public static MaybeX<T> Nothing { get { return default(MaybeX<T>); } }
///<summary>Create a new MaybeX{T}.</summary>
private MaybeX(T value) : this() {
_value = value;
}
/// <summary>LINQ-compatible implementation of the monadic map operator.</summary>
///<remarks>
/// Used to implement the LINQ <i>let</i> clause and queries with a single FROM clause.
///
/// Always available from Bind():
/// return @this.Bind(v => projector(v).ToMaybe());
///</remarks>
public MaybeX<TResult> Select<TResult>(
Func<T, TResult> projector
) where TResult : class {
projector.ContractedNotNull(nameof(projector));
return (_value == null) ? default(MaybeX<TResult>) : projector(_value);
}
///<summary>The monadic Bind operation of type T to type MaybeX{TResult}.</summary>
/// <remarks>
/// Convenience method - not used by LINQ
/// </remarks>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
[Pure]
public MaybeX<TResult> SelectMany<TResult>(
Func<T, MaybeX<TResult>> selector
) where TResult:class {
selector.ContractedNotNull(nameof(selector));
return (_value == null) ? default(MaybeX<TResult>) : selector(_value);
}
/// <summary>LINQ-compatible implementation of the monadic join operator.</summary>
/// <remarks>
/// Used for LINQ queries with multiple <i>from</i> clauses or with more complex structure.
/// </remarks>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
public MaybeX<TResult> SelectMany<TIntermediate, TResult>(
Func<T, MaybeX<TIntermediate>> selector,
Func<T,TIntermediate,TResult> projector
) where TIntermediate:class where TResult:class {
selector.ContractedNotNull(nameof(selector));
projector.ContractedNotNull(nameof(projector));
var @this = this;
return (_value == null) ? default(MaybeX<TResult>)
: selector(_value).Select(e => projector(@this._value, e));
}
///<summary>Returns whether this MaybeX{T} has a value.</summary>
public bool HasValue {
get {
Ensures((_value != null) == HasValue);
return _value != null;
}
}
///<summary>Extract value of the MaybeX{T}, substituting <paramref name="defaultValue"/> as needed.</summary>
[Pure]
public T BitwiseOr(T defaultValue) {
defaultValue.ContractedNotNull(nameof(defaultValue));
Ensures(Result<T>() != null);
return _value ?? defaultValue;
}
///<summary>Extract value of the MaybeX{T}, substituting <paramref name="defaultValue"/> as needed.</summary>
[Pure]
public static T operator | (MaybeX<T> value, T defaultValue) {
defaultValue.ContractedNotNull(nameof(defaultValue));
Ensures(Result<T>() != null);
return value.BitwiseOr(defaultValue);
}
///<summary>The invariants enforced by this struct type.</summary>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
[ContractInvariantMethod]
[Pure]
private void ObjectInvariant() {
Invariant(HasValue == (_value != null));
}
///<summary>Wraps a T as a MaybeX{T}.</summary>
[Pure]
public static implicit operator MaybeX<T>(T value) => new MaybeX<T>(value);
readonly T _value;
#region Value Equality with IEquatable<T>.
/// <inheritdoc/>
[Pure]
public override bool Equals(object obj) => (obj as MaybeX<T>?)?.Equals(this) ?? false;
/// <summary>Tests value-equality, returning <b>false</b> if either value doesn't exist.</summary>
[Pure]
public bool Equals(MaybeX<T> other) =>
_value != null ? other._value != null && (_value == other._value || _value.Equals(other._value))
: other._value == null;
///<summary>Retrieves the hash code of the object returned by the <see cref="_value"/> property.</summary>
[Pure]
public override int GetHashCode() => (_value == null) ? 0 : _value.GetHashCode();
/// <summary>Tests value-equality, returning false if either value doesn't exist.</summary>
[Pure]
public static bool operator == (MaybeX<T> lhs, MaybeX<T> rhs) => lhs.Equals(rhs);
/// <summary>Tests value-inequality, returning false if either value doesn't exist..</summary>
[Pure]
public static bool operator != (MaybeX<T> lhs, MaybeX<T> rhs) => ! lhs.Equals(rhs);
///<summary>Tests value-equality, returning <see cref="null"/> if either value doesn't exist.</summary>
[Pure]
public bool? AreNonNullEqual(MaybeX<T> rhs) =>
this.HasValue && rhs.HasValue ? this._value.Equals(rhs._value)
: null as bool?;
///<summary>Tests value-equality, returning <see cref="null"/> if either value doesn't exist.</summary>
[Pure]
public bool? AreNonNullUnequal(MaybeX<T> rhs) =>
this.HasValue && rhs.HasValue ? ! this._value.Equals(rhs._value)
: null as bool?;
#endregion
/// <inheritdoc/>
[Pure]
public override string ToString() {
Ensures(Result<string>() != null);
return SelectMany<string>(v => v.ToString()) | "";
}
}
[Pure]
public static class MaybeX {
///<summary>Amplifies a reference-type T to a MaybeX{T}.</summary>
///<remarks>The monad <i>unit</i> function.</remarks>
public static MaybeX<T> AsMaybeX<T>(this T @this) where T:class => @this;
///<summary>Amplifies a reference-type T to a MaybeX{T}.</summary>
///<remarks>The monad <i>unit</i> function.</remarks>
public static MaybeX<object> ToMaybeX<T>(this T @this) where T : struct => @this;
///<summary>Returns the type of the underlying type {TValue}.</summary>
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "this")]
public static Type GetUnderlyingType<T>(this MaybeX<T> @this) where T:class {
Ensures(Result<System.Type>() != null);
return typeof(T);
}
public static MaybeX<T> Cast<T>(this MaybeX<object> @this) where T:class =>
from o in @this select (T)o;
}
/// <summary>Extension methods to enhance Code Contracts and integration with Code Analysis.</summary>
[Pure]
public static class ContractExtensions {
/// <summary>Throws <c>ContractException{name}</c> if <c>value</c> is null.</summary>
/// <param name="value">Value to be tested.</param>
/// <param name="name">Name of the parameter being tested, for use in the exception thrown.</param>
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "value")]
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "name")]
[ContractAbbreviator] // Requires Assemble Mode = Standard Contract Requires
[DebuggerStepThrough]
#if DEBUG
[MethodImpl(MethodImplOptions.NoInlining)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static void ContractedNotNull<T>([ValidatedNotNull]this T value, string name) {
Requires(value != null, name);
}
/// <summary>Decorator for an object which is to have it object invariants assumed.</summary>
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "t")]
public static void AssumeInvariant<T>(this T t) { }
/// <summary>Decorator for an incoming parameter that is contractually enforced as NotNull.</summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class ValidatedNotNullAttribute : Attribute {}
}
}
Ответ 3
Вы можете сделать что-то вроде этого:
var getter = parent != null ?
parent.Child != null ?
parent.Child.Child != null ?
parent.Child.Child
: null : null : null;
//or
var getter2 = parent == null ? null :
parent.Child == null ? null :
parent.Child.Child == null ? null :
parent.Child.Child;
Если родительский объект совпадает с дочерним классом, вы можете построить расширение, которое проверяет, является ли оно нулевым, но оно должно будет создать новый объект... вы можете, возможно, уничтожить этот объект после проверки. Что-то вроде этого:
public class ParentClass
{
public ParentClass(bool flag = false)
{
this.NullFlag = flag;
}
public ParentClass Child { get; set; }
public readonly bool NullFlag { get; set; }
}
public static class ParentClassExtenstion
{
public static ParentClass GetChild(this ParentClass parent)
{
if (parent.Child == null)
{
parent.Child = new ParentClass(true);
}
return parent.Child;
}
}
И затем используйте как:
var getter3 = parent.GetChild().GetChild().GetChild();
if (!getter3.NullFlag)
{
//safe;
}