Wie kann man die Kompilierungszeit für Werttypen in LINQ / lambdas / Ausdrucksbäumen überprüfen?

.net c# expression-trees lambda linq

Frage

Ich verwende den folgenden Code, um Control Eigenschaften threadsicher festzulegen:

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

Es heißt so:

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

Der Grund dafür besteht darin, Kompilierungszeitprüfungen von Eigenschaftsnamen und Typzuweisungen zu erhalten. Es funktioniert perfekt für Standardobjekte, aber bei Werttypen ist alles ein wenig birnenförmig, weil der Compiler gerne folgendes akzeptiert, was natürlich zur Laufzeit bombardiert:

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

Ich habe bereits die SetPropertyThreadSafe Methode geändert, um den Fall zu behandeln, wenn SetPropertyThreadSafe verwendet werden, und eine Ausnahme SetPropertyThreadSafe , wenn der falsche Typ als Argument verwendet wird. Was ich wirklich suche, ist die Fähigkeit, diese Methode zum Kompilieren zu bekommen -Time-Type-Prüfung für 100% der Fälle, dh Objekte und Werttypen. Ist das überhaupt möglich und wenn ja, wie würde ich meinen Code ändern müssen, um dies zu tun?

Akzeptierte Antwort

Ändern Sie den Vertrag zu:

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

Beachten Sie, dass Sie damit die IsAssignableFrom-Prüfung nicht mehr durchführen müssen, da der Compiler sie erzwingen wird.

Die Gründe für die Kompilierung dieses Beispiels sind, dass der Compiler eine Schätzung vorgenommen hat, was der Typparameter ist. Hier ist, worauf der Compiler diese Aufrufe ausrichtet:

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

Beachten Sie, dass die erste int ist, weil ProgressBar.Step ein int ist und "c" ein char ist, das eine implizite Umwandlung in int hat. Dasselbe gilt für das nächste Beispiel. Int hat eine implizite Umwandlung in long und die zweite ist lang, sodass der Compiler davon ausgeht, dass es lang ist.

Wenn Sie möchten, dass Vererbung und Konvertierungen wie diese funktionieren, lassen Sie den Compiler nicht raten. Ihre zwei Lösungen sind:

  1. Geben Sie immer den Typ Parameter an. In diesem Fall hätten Sie bemerkt, dass der zweite lang ist, und das Problem behoben.

Das ist natürlich weniger als ideal, denn dann sind Sie grundsätzlich in der Art des Func fest programmiert. Was Sie wirklich tun wollen, ist, dass der Compiler beide Typen bestimmt und Ihnen sagt, ob sie kompatibel sind.

  1. Geben Sie für beide einen anderen Typ an, damit der Compiler es für Sie herausfinden kann.

Hinweis: Im Folgenden ist der Code, den ich verwenden würde, der ganz anders als Ihr ist:

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

Beliebte Antwort

Sie müssen lediglich einige kleinere Änderungen an Ihrem generischen Ausdruck vornehmen:

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

Dann liefern Sie ein Lambda wie folgt:

var someObject = new /*Your Object*/

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

Der von Ihnen angegebene Wert muss kovariant zum Typ von SomeProperty sein. Dies wird zur Kompilierungszeit überprüft. Lass es mich wissen, wenn ich etwas falsch verstehe. Wenn Sie es auf Control beschränken müssen, ändern Sie einfach die Signatur in

this Control source

oder

where TSource : Control


Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow