2016-05-29 18 views
10

È possibile creare un metodo di estensione in C# 5.0 per ottenere gli stessi risultati dell'operatore Elvis (?.) C# 6.0?Metodo di estensione Elvis (?.) In C# 5.0

Ad esempio:

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

operatore Elvis, lo amano. – Steve

+4

Questo non è l'operatore Elvis. Vedi: https://en.wikipedia.org/wiki/Elvis_operator Lo chiamo "l'operatore Elvis con un occhio solo" –

+0

'?:' È l'operatore condizionale in C#: https://msdn.microsoft.com/it us/library/ty67wk28.aspx '? .' è un operatore condizionale nullo: https://msdn.microsoft.com/en-us/library/dn986595.aspx?f=255&MSPPError=-2147217396 –

risposta

8

Potrebbe essere possibile utilizzando la stessa metodologia scherno (che invece di ritornare parent, sarebbe restituire un 'finto' del genitore che sia di ritorno nullo, o gli oggetti valore). Tuttavia, questo diventa un po 'più complicato.

Questo è abbastanza semplice, e ottiene il lavoro funzionalità di base:

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

provarla:

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

ottiene correttamente l'oggetto Person.

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

Resi nulli.

Tuttavia, questo funziona solo per i tipi nullable. Sfortunatamente, non è possibile creare un sovraccarico per where TReturnType : struct, abbiamo bisogno di un nuovo metodo per gestirlo.

Così, per i tipi non nullable, abbiamo bisogno di questo:

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

E provarla:

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

Si può naturalmente fare qualcosa di simile:

 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; 

Se il genitore è uguale alla classe figlia, è possibile creare un'estensione che controlli se nullo, ma dovrà creare un nuovo oggetto ... n forse gettare quell'oggetto dopo il controllo. Qualcosa di simile a questo:

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

e quindi utilizzare come:

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

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

Siamo spiacenti, ma la risposta dovrebbe dare una soluzione metodo di estensione. –

+0

Usa l'estensione, vuoi dire che non puoi aggiungere proprietà alla classe? –

+0

Come il vero Elvis - dovrebbe essere un'estensione per qualsiasi tipo di oggetto. –

3

Sì; è il monadico mappa operator ed è chiamato Select() in LINQ. Dopo aver aggiunto l'altro metodo di estensione LINQ correlato, l'operatore di appiattimento monodico SelectMany(), è possibile iniziare a utilizzare la sintassi di comprensione di LINQ per fare in modo che Elvis dondoli davvero i fianchi.

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

produce in uscita:

String45 String33 
Nothing 

Il passo successivo è quello di avvolgere il tutto in una struttura per non esporre il riferimento nudo; aggiungere Contratti di Codice per pubblicizzare sui clienti i risultati non annullabili; e preservare Valore uguaglianza in base al tipo di fondo, e si è vestita l'oggetto nuda fa riferimento in quello che sembra notevolmente come un tipo di riferimento non annullabile:

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 {} 
    } 
} 
+0

Pieter, come si usa dopo il "prossimo passo"? –

+0

@ N.D.B: esempi della soluzione completa sul mio gitHub: https://github.com/pgeerkens/Monads. Guarderò postare qualcosa qui stasera dopo il lavoro. –

Problemi correlati