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?
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:
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.
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);
}
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