How may value types in LINQ/lambdas/expression trees be checked at compile time?

.net c# expression-trees lambda linq

Question

I'm use the code below to establishControl attributes in a way that is 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 });
  }
}

It goes by the name of:

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

This is being done in order to have property names and type assignments checked at build time. When it comes to value types, though, things goes a little awry since the compiler is willing to accept the following, which of course fails at runtime:

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

I had changed theSetPropertyThreadSafe The ability to get this function to do compile-time type checking for every situation, i.e. objects and value types, is what I'm really looking for. There are methods to handle the case where value types are used and raise an exception if the erroneous type is provided as an input. Is this even feasible, and if so, how would I change my code to make it happen?

1
1
4/15/2011 7:58:06 PM

Accepted Answer

Modify the contract such that:

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

It should be noted that since the compiler will enforce it, you no longer need to do the IsAssignableFrom check.

Because the compiler guessed what the type argument was, your example was able to build. What the compiler produces from those calls is as follows:

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

The first one is an int because ProgressBar, as you may have seen. Step is an int, whereas "c" is a char that is implicitly converted to an int. The following example is similar; because the second one is long and int contains an implicit conversion to long, the compiler infers that it is long.

Don't let the compiler to guess if you want conversions like those and inheritance to work. You have two options:

  1. Type parameters should always be specified. In this instance, you would have identified the second one's length as an issue and remedied it.

This is obviously not good since it effectively hard codes the type of the Func. The best course of action is to let the compiler assess if the two types are compatible.

  1. In order for the compiler to figure it out, give each a separate type.

NOTE: The code I would use, which is completely different from yours, is provided below:

    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);
    }
3
4/25/2011 11:56:24 AM

Popular Answer

You just need to tweak your generic and expression slightly:

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

Next, you give a lambda as follows:

var someObject = new /*Your Object*/

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

It is verified at compilation time that the value you supply must be covariant with the type of SomeProperty. If I'm misinterpreting anything, please let me know. Change the signature to "Control" if you need to restrict it.

this Control source

or

where TSource : Control


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow