Come ottenere il controllo in fase di compilazione per i tipi di valore in LINQ / lambdas / alberi di espressione?

.net c# expression-trees lambda linq

Domanda

Sto usando il seguente codice per impostare le proprietà di Control in modo thread-safe:

private delegate void SetPropertyThreadSafeDelegate<TPropertyType>(Control @this, Expression<Func<TPropertyType>> property, TPropertyType value);

public static void SetPropertyThreadSafe<TPropertyType>(this Control @this, Expression<Func<TPropertyType>> property, TPropertyType value)
{
  var propertyInfo = (property.Body as MemberExpression ?? (property.Body as UnaryExpression).Operand as MemberExpression).Member as PropertyInfo;

  if (propertyInfo == null ||
      !propertyInfo.ReflectedType.IsAssignableFrom(@this.GetType()) ||
      @this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
  {
    throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
  }

  if (propertyInfo.PropertyType.IsValueType &&
      !propertyInfo.PropertyType.IsAssignableFrom(typeof(TPropertyType)))
  {
    throw new ArgumentException(string.Format("Attempted to assign incompatible value type: expecting {0}, got {1}.", propertyInfo.PropertyType, typeof(TPropertyType)));
  }

  if (@this.InvokeRequired)
  {
    @this.Invoke(new SetPropertyThreadSafeDelegate<TPropertyType>(SetPropertyThreadSafe), new object[] { @this, property, value });
  }
  else
  {
    @this.GetType().InvokeMember(propertyInfo.Name, BindingFlags.SetProperty, null, @this, new object[] { value });
  }
}

Si chiama così:

downloadProgressBar.SetPropertyThreadSafe(() => downloadProgressBar.Step, 32);

Il motivo per farlo è ottenere il controllo in fase di compilazione dei nomi delle proprietà e delle assegnazioni dei tipi. Funziona perfettamente per oggetti standard, ma tutto è un po 'a forma di pera con tipi di valore perché il compilatore è felice di accettare quanto segue, che ovviamente bombe in fase di runtime:

downloadProgressBar.SetPropertyThreadSafe(() => downloadProgressBar.Step, 'c');
downloadProgressBar.SetPropertyThreadSafe(() => downloadProgressBar.Step, long.MaxValue);

Ho già modificato il metodo SetPropertyThreadSafe per gestire il caso in cui vengono utilizzati i tipi di valore e genera un'eccezione se il tipo errato viene utilizzato come argomento, ma quello che sto veramente cercando è la possibilità di ottenere questo metodo per eseguire la compilazione -time tipo di controllo per il 100% dei casi, cioè oggetti e tipi di valore. E 'anche possibile e se sì, come dovrei modificare il mio codice per fare questo?

Risposta accettata

Cambia il contratto in:

public static void SetPropertyThreadSafe<TPropertyType, TValue>(
        this Control self,
        Expression<Func<TPropertyType>> property,
        TValue value)
        where TValue : TPropertyType

Nota che con questo non hai più bisogno di fare il controllo IsAssignableFrom poiché il compilatore lo applicherà.

Le ragioni per cui il tuo esempio è stato compilato è perché il compilatore ha fatto un'ipotesi su quale fosse il parametro type. Ecco cosa il compilatore trasforma in queste chiamate:

progBar.SetPropertyThreadSafe<int>(() => progBar.Step, 'c');
progBar.SetPropertyThreadSafe<long>(() => progBar.Step, long.MaxValue);

Notate come il primo è int, perché ProgressBar.Step è un int e 'c' è un char che ha una conversione implicita in int. Lo stesso con l'esempio successivo, int ha una conversione implicita a long e il secondo è lungo, quindi il compilatore indovina che è lungo.

Se vuoi che l'ereditarietà e le conversioni come quelle funzionino, non indovinare il compilatore. Le tue due soluzioni sono:

  1. Specifica sempre il tipo perameter. In questo caso, avresti notato che il secondo è lungo e ha risolto il problema.

Ovviamente questo non è l'ideale, perché in questo caso sei praticamente codificante nel tipo di Func. Quello che vuoi veramente fare è lasciare che il compilatore determini entrambi i tipi e ti dica se sono compatibili.

  1. Fornisci un tipo diverso per entrambi in modo che il compilatore possa capirlo.

NOTA: Di seguito è riportato il codice che utilizzerei, che è completamente diverso dal tuo:

    public static void SetPropertyThreadSafe<TControl>(this TControl self, Action<TControl> setter)
        where TControl : Control
    {
        if (self.InvokeRequired)
        {
            var invoker = (Action)(() => setter(self));
            self.Invoke(invoker);
        }
        else
        {
            setter(self);
        }
    }

    public static void Example()
    {
        var progBar = new ProgressBar();
        progBar.SetPropertyThreadSafe(p => p.Step = 3);
    }

Risposta popolare

Hai solo bisogno di apportare alcune modifiche minori al tuo generico e all'espressione:

public static void SetPropertyThreadSafe<TSource, TPropertyType>(this TSource source, Expression<Func<TSource, TPropertyType>> property, TPropertyType value)

Quindi fornisci un lambda in questo modo:

var someObject = new /*Your Object*/

someObject.SetPropertyThreadSafe(x => x.SomeProperty, /* Your Value */);

Il valore specificato deve essere covariante al tipo di SomeProperty e viene verificato in fase di compilazione. Fammi sapere se sto fraintendendo qualcosa. Se è necessario vincolarlo a Control, è sufficiente modificare la firma in

this Control source

o

where TSource : Control


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché