Изменить контракт на:
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.
Если вы хотите, чтобы наследование и преобразования, подобные этим, работали, не пытайтесь угадать компилятор. Ваши два решения:
- Всегда указывайте тип пераметра. В этом случае вы бы заметили, что второй длинный, и устранили проблему.
Конечно, это далеко не идеально, потому что тогда вы в основном жестко программируете в типе Func. Что вы действительно хотите сделать, так это позволить компилятору определить оба типа и сообщить вам, совместимы ли они.
- Предоставьте разные типы для обоих, чтобы компилятор мог понять это за вас.
ПРИМЕЧАНИЕ. Ниже приведен код, который я бы использовал, который полностью отличается от вашего:
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);
}