简体   繁体   中英

Elvis (?.) Extension Method in C# 5.0

Is it possible to create some extension method in C# 5.0 to give the same results as the C# 6.0 Elvis (?.) operator?

For example:

//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

It might be possible using the same methodology as mocking (that instead of returning parent , it would return a 'mock' of the parent which would either return null, or the objects value). However, that gets a bit more complicated.

This is fairly straight forward, and gets the basic functionality working:

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);
    }
}

Testing it:

var person = new Person { Parent = new Person { Parent = new Person() } };
var result = person.Elvis(p => p.Parent).Elvis(p => p.Parent);

Correctly gets the Person object.

person = new Person();
result = person.Elvis(p => p.Parent).Elvis(p => p.Parent);

Returns null.

However, this only works for nullable types. Unfortunately, you can't create an overload for where TReturnType : struct , we need a new method to handle it.

So, for non-nullable types, we need this:

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);
}

And testing it:

var result = person.Elvis(p => p.Parent).Elviss(p => p.Id);

Yes; it is the monadic map operator and is called Select() in LINQ. Once you add the other related LINQ extension method, the monadic flatten operator SelectMany(), you can start using LINQ comprehension syntax to make Elvis really swing his hips.

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)); ;
        }
    }
}

produces as output:

String45 String33
Nothing

The next step is to wrap the whole thing in a struct in order to not expose the naked reference; add Code Contracts to advertise to clients the non-nullability results; and preserve Value Equality based on the underlying Type, and one has clothed the naked object references in what looks remarkably like a non-nullable reference type:

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's 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 {}
    }
}

You can ofcourse do something like this:

        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;

If the parent is the same as the child class, you can build an extension that check if null, but it will has to make new object... you can maybe dispose that object after the check. Something like this:

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;
    }
}

And then use like:

        var getter3 = parent.GetChild().GetChild().GetChild();

        if (!getter3.NullFlag) 
        {
            //safe;
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM