Как получить проверку во время компиляции для типов значений в деревьях LINQ / lambdas / expression? - PullRequest
1 голос
/ 15 апреля 2011

Я использую следующий код для установки Control свойств потокобезопасным способом:

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

Это называется так:

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

Причиной этого является проверка во время компиляции имен свойств и назначений типов. Он отлично работает для стандартных объектов, но все идет немного грушевидно с типами значений, потому что компилятор с радостью принимает следующее, что, конечно, взрывает во время выполнения:

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

Я уже изменил метод SetPropertyThreadSafe для обработки случая, когда используются типы значений, и выкинул исключение, если в качестве аргумента используется неправильный тип, но то, что я действительно отыскиваю, это возможность получить этот метод для выполнения проверки типов во время компиляции для 100% случаев, то есть для объектов и типов значений. Возможно ли это вообще, и если да, то как мне нужно изменить свой код для этого?

Ответы [ 3 ]

3 голосов
/ 16 апреля 2011

Изменить контракт на:

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

Обратите внимание, что при этом вам больше не нужно выполнять проверку IsAssignableFrom, так как компилятор будет применять ее.

Причины, по которым ваш пример скомпилирован, состоят в том, что компилятор сделал предположение относительно того, каким был параметр типа. Вот что компилятор превращает в эти вызовы:

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

Обратите внимание, что первым является int, потому что ProgressBar.Step - это int, а 'c' - это символ, который имеет неявное преобразование в int. Как и в следующем примере, int имеет неявное преобразование в long, а второй - в long, поэтому компилятор догадывается, что это long.

Если вы хотите, чтобы наследование и преобразования, подобные этим, работали, не пытайтесь угадать компилятор. Ваши два решения:

  1. Всегда указывайте тип пераметра. В этом случае вы бы заметили, что второй длинный, и устранили проблему.

Конечно, это далеко не идеально, потому что тогда вы в основном жестко программируете в типе Func. Что вы действительно хотите сделать, так это позволить компилятору определить оба типа и сообщить вам, совместимы ли они.

  1. Предоставьте разные типы для обоих, чтобы компилятор мог понять это за вас.

ПРИМЕЧАНИЕ. Ниже приведен код, который я бы использовал, который полностью отличается от вашего:

    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);
    }
0 голосов
/ 16 апреля 2011

Вам просто нужно внести некоторые незначительные изменения в ваш универсальный и выражение:

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

Тогда вы поставляете лямбду следующим образом:

var someObject = new /*Your Object*/

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

Указанное вами значение должно быть ковариантным типу SomeProperty, и это проверяется во время компиляции. Дайте мне знать, если я что-то неправильно понимаю. Если вам нужно ограничить его контролем, просто измените подпись на

this Control source

или

where TSource : Control
0 голосов
/ 16 апреля 2011

Вы не можете.Linq использует деревья выражений, которые оцениваются во время выполнения.

Я бы предложил создать модульные тесты для ваших запросов.

...