Comment obtenir une vérification au moment de la compilation pour les types de valeur dans les arbres LINQ / lambdas / expression?

.net c# expression-trees lambda linq

Question

J'utilise le code suivant pour définir Control propriétés du Control de manière 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 });
  }
}

Ça s'appelle comme ça:

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

La raison à cela est d’obtenir la vérification au moment de la compilation des noms de propriété et des affectations de types. Cela fonctionne parfaitement pour les objets standard, mais tout va un peu en forme de poire avec les types de valeur, car le compilateur accepte volontiers les éléments suivants, qui bombardent bien sûr lors de l'exécution:

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

J'ai déjà modifié la méthode SetPropertyThreadSafe pour gérer le cas où des types de valeur sont utilisés et émettre une exception si le type incorrect est utilisé comme argument, mais ce que je recherche vraiment, c'est la possibilité d'obtenir cette méthode pour compiler vérification de type en temps réel pour 100% des cas, c'est-à-dire des objets et des types de valeur. Est-ce même possible et si oui comment aurais-je besoin de modifier mon code pour le faire?

Réponse acceptée

Changer le contrat en:

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

Notez qu'avec cela, vous n'avez plus besoin de faire la vérification IsAssignableFrom puisque le compilateur l'imposera.

Les raisons pour lesquelles votre exemple a été compilé sont dues au fait que le compilateur a deviné le type du paramètre type. Voici ce que le compilateur transforme ces appels en:

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

Notez que le premier est int, c'est parce que ProgressBar.Step est un int et 'c' est un caractère qui a une conversion implicite en int. Idem avec l'exemple suivant, int a une conversion implicite en long, et le second est long, le compilateur devine qu'il est long.

Si vous voulez que l'héritage et les conversions comme ceux-là fonctionnent, ne laissez pas le compilateur deviner. Vos deux solutions sont:

  1. Toujours spécifier le type de paramètre. Dans ce cas, vous auriez remarqué que le second est long et corrigé le problème.

Bien sûr, cela n’est pas idéal, car vous codez durement dans le type de Func. Ce que vous voulez vraiment faire est de laisser le compilateur déterminer les deux types et vous dire s'ils sont compatibles.

  1. Fournissez un type différent pour les deux afin que le compilateur puisse le comprendre pour vous.

NOTE: Voici le code que je voudrais utiliser, qui est complètement différent du vôtre:

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

Réponse populaire

Vous devez juste apporter quelques modifications mineures à votre expression générique et:

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

Ensuite, vous fournissez un lambda comme ceci:

var someObject = new /*Your Object*/

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

La valeur que vous spécifiez doit être covariante au type de SomeProperty, et ceci est vérifié lors de la compilation. Faites-moi savoir si je comprends mal quelque chose. Si vous devez le contraindre à Contrôler, il vous suffit de changer la signature pour

this Control source

ou

where TSource : Control


Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow