none
Prüfen ob System.Type eine Zahl ist RRS feed

  • Frage

  • Hallo zusammen,

    diesmal habe ich eher eine Anfängerfrage.

    Und zwar habe ich mehrer Instanzen von System.Type. Über diese Objekte möchte iterieren und möchte prüfen, ob diese die Standardwerte für ihren jeweiligen Typen enthalten, also z.B. 0 für Zahlentypen, DateTime.MinValue für DateTime und Guid.Empty für Guid Instanzen. Wie kann ich möglichst einfach prüfen, ob ein System.Type eine Zahl bzw. Fließkommazahl darstellt?

    Mein Lösungsansatz war der, dass ich per "IsValueType" und anschliessend mit dem "is" Operator geprüft habe. Aber es kann doch nicht sein, dass ich jede einzelne Instanz gegen jeden möglichen Datentypen einzeln prüfen muss?! Oder gibt es einen geschickteren Weg.

    Vielen Dank für Eure Hilfe :o)

    Viele Grüße

    Holger M. Rößler

    Mittwoch, 8. September 2010 17:48

Antworten

  • Hallo Holger,

    dann würde ich das schon eher so machen (ungetestet):

      public static bool IsNumericType(Type type)
    	{
    		if (type == null)
    			return false;
    
    		switch (Type.GetTypeCode(type))
    		{
    			case TypeCode.SByte: 
    			case TypeCode.Byte: 
    			case TypeCode.Int16: 
    			case TypeCode.UInt16: 
    			case TypeCode.Int32: 
    			case TypeCode.UInt32:
    			case TypeCode.Int64: 
    			case TypeCode.UInt64: 
    			case TypeCode.Single: 
    			case TypeCode.Double: 
    			case TypeCode.Decimal:
    				return true;
    				
    			case TypeCode.String: 
    			case TypeCode.Char: 
    			case TypeCode.Boolean: 
    			case TypeCode.DateTime: 
    			case TypeCode.DBNull: 
    			default:
    				return false;
    		}
    	}
    

    denn switch wird hier vom C# Compiler über eine Sprungtabelle realisiert, was sehr fix ist.
    Hast Du Nullable-Types käme da noch etwas mehr hinzu.

    Gruß Elmar

    Samstag, 11. September 2010 20:55
    Beantworter

Alle Antworten

  • Hallo Holger

    Details deiner Frage sind nicht ganz klar (kleines Code-Bsp wäre von Vorteil),
    aber evtl. hilft einer der folgenden Links
    http://stackoverflow.com/questions/65351/null-or-default-comparsion-of-generic-argument-in-c
    http://stackoverflow.com/questions/2187471/default-value-check-using-generic-types

    aber alles davon was Richtung Reflection/LateBinding (oä) geht, dürfte wenig Performant sein (oder etliches an Laufzeit-Code-Overhead).

    Mittwoch, 8. September 2010 18:08
  • Hi Thomas,

    richtig. Aber damit lassen sich extrem flexible APIs bauen. Ich arbeite gerade an einem Persistenzframework, das quasi nur auf Reflektion + Emit basiert. Dafür ist die Codemenge die später für persistierung geschrieben werden muss, nahezu null. So werden z.B. Entitäten on the fly erzeugt. Auch Änderungen an der Datenbankstruktur z.B. ziehen keine Änderungen im Code nach sich, da das Framework den ganzen Code zur Laufzeit generiert.

    Aber zurück zum Thema. Hier ist mal eine Zeile Code, wie ich die Typen momentan prüfe. Ich finde das halt sehr umständlich:

    if(column.Value is ValueType && 
      column.Value is long && 
      ((long) column.Value == default(long))) {
     ...
    }
    

    Vielen Dank und viele Grüße

    Holger M. Rößler

    Mittwoch, 8. September 2010 19:34
  • Hallo Holger,

    ich bin noch .Net-Neuling und mir ist in deinem Codebeispiel einiges nicht klar.

    Wenn z. B. long vom Typ ValueType ist bzw. davon abgeleitet ist, warum die erst die Prüfung auf ValueType und dann auf long. Ist eine Prüfung nur auf long nicht ausreichend?

    Wenn default(T) den Defaultwert für T zurückliefert, könnte man doch beiden Werte mit Equals vergleichen, z. B.:

       if (default(long).Equals(column.Value))
       {
        ...
       }
    
    

    Damit wäre die sowohl Typprüfung und die Prüfung von column.Value gegen null in einer Zeile erledigt.

    Grüße

    Peter

    Donnerstag, 9. September 2010 15:00
  • Hi Peter,

    ich gebe dir recht! Der Codeschnipsel war nicht ideal! 

    if(column.Value is ValueType) { 
    if(column.Value is long && ((long) column.Value) == default(long)) {
    } else if(column.Value is decimal && ((decimal) column.value == default(decimal))) {
    } else if...
     ...
    }
    
    

    Das weitere Prüfen auf die Typen macht nur Sinn, wenn column.Value  von ValueType abgeleitet ist.

    Aber dein Beispiel ist garnicht so dumm! Es kommt doch garnicht unbedingt darauf an, den Typen genau zu kennen, sondern nur, ob column.Value den Standardwert für long (für die Ganzzahlen) bzw. decimal (für Fließkommazahlen) hat. Das werde ich Ausprobieren.

    Ich melde mich diesbezüglich nochmals.

    Vielen Dank für diesen Tip!

    Viele Grüße

    Holger M. Rößler

    Freitag, 10. September 2010 06:32
  • Hallo Holger,

    hast Du Dir Thomas Links genauer angesehen?

    Soweit man das anhand Deines Codeschnipsels deuten kann,
    sollte der Vergleich via EqualityComparer (aus dem zweiten Link) zutreffen,
    wenn sicn der Vergleicn auf ValueType (ohne Nullable) beschränkt:

    public bool EqualsDefaultValue<T>(T value)
    {
      return EqualityComparer<T>.Default.Equals(value, default(T));
    }
    
    
    Gruß Elmar

    Freitag, 10. September 2010 07:05
    Beantworter
  • Hallo Holger.

    Wenn ich dich richtig verstanden habe, soll die ValueType-Prüfung nur verhindern, dass die ganze if-else-Kette unnötig durchlaufen wird, und in den einzelnen if-else-Blöcken wird Code ausgeführt der unabhängig vom Typ ist.

    Dann könntest du auch die zu prüfenden Typen in ein Array zu geben und den Wert gegen dieses Array prüfen, z. B.:
    using System;
    using System.Linq;
    
      public static class DefaultChecker
      {
        private static Object[] DefaultValueTypes = new object[4] {default(int), default(double), default(long), default(DateTime)};
    
        public static bool IsDefaultValueType(object value)
        {
          if (value is ValueType)
            return DefaultValueTypes.Any(o => o.Equals(value));
          else
            return false;
        }
      }
    
    
    und abfragen mit
          if (DefaultChecker.IsDefaultValueType(column.Value))
          {
            // typunabhängiger Code
          }
    
    

    Schöne Grüße

    Peter

    Freitag, 10. September 2010 12:31
  • Hallo Peter,

    das wird relativ ineffizient, da zum einen über eine (effektiv größere) Auflistung
    enumeriert würde und zudem ein Cast fällig ist.

    Gruß Elmar

    Freitag, 10. September 2010 15:38
    Beantworter
  • Hallo Elmar!

    Bei

    if EqualsDefaultValue<long>((long) column.Value) 
    {
    }
    else if EqualsDefaultValue<decimal>((decimal) column.Value) 
    {
    ...
    

    ist ja auch für jede Prüfung ein Cast fällig.
    Oder kann man EqualsDefaultValue<T>(T value) auch ohne Cast aufrufen und ein korrektes Ergebnis erhalten?

    Grüße

    Peter

    Freitag, 10. September 2010 17:34
  • Hallo Elmar, hallo Peter,

    vielen Dank für Eure Bemühungen und eure Tips! :o)

    Anhand eurer Diskussion habt ihr selbst gemerkt, dass das Thema doch nicht so einfach ist, wie ich es Anfangs dachte. Hier besteht meiner Ansicht nach Nachbesserungsbedarf seitens Microsoft. Die Typprüfungen für die einfachen Typen fehlen schlichtweg und sind nur sehr umständlich nachzubauen.

    Was haltet Ihr von der Idee, die Prüfung per Erweiterungsmethoden in System.Type zu injizieren? So könnte ich zum Beispiel eine Methode "IsNumeric()" definieren, und den Code darin implementieren. Die Prüfungen wären zwar weiterhin recht umständlich und auch ineffizient, aber die Verwendung von System.Type im "Produktionscode" wäre doch deutlich kompakter, lesbarer und verständlicher als dieses if - else if - else if wirrwarr.

    Vielen Dank und viele Grüße

    Holger M. Rößler

    Freitag, 10. September 2010 21:18
  • Hallo Holger,

    mein Beitrag gestern war mehr ein Schuß ins Dunkle ;-)

    Mir fehlt bei Deinem Beispiel - wie auch Thomas - der nähere Verwendungszweck,
    oben deutest Du nur Reflektion + Emit an. Der Vergleich alleine hat wenig Aussagekraft.

    Dort gibt es mehrere Wege, je nachdem was man erreichen will.
    An einigen Stellen benötigt man nicht einmal ein genaues Wissen über den Typ
    und kann es dem Jitter überlassen, das umzusetzen.
    Der Standardwert für die integrierten Typen ist auf Byte-Ebene eine Anzahl
    von Nul-Bytes (0), die in Abhängigkeit von der Größe des Typs stehen.
    In IL werden dafür Erweiterungen vorgenommen, siehe z. B. OpCode.Ldarg
    und andere Ld... OpCodes.
    Dazu gibt es bereits vielfach Beispiele, z. B. auf Codeproject.

    Der erwähnte EqualityComparer wertet z. B. System.RuntimeType aus,
    die jedoch intern ist und man besser mit Type arbeitet.

    Zudem gibt es TypeCode , was man via switch relativ effizient auswerten kann
    und häufiger genutzt wird (Reflector -> Used By).

    Die Möglichkeiten sind insofern vorhanden. Aauch die Microsoft Entwickler kommen
    damit klar, siehe Entity Framework - und bei NHibernate darfst Du auch die
    Quellen begucken. Andere wie AOP Frameworks dürften (nie geguckt)
    noch heftiger in die Trickkiste greifen ;-)

    Von Erweiterungsmethoden würde ich absehen.
    Schon ValueType ist in manchen Fällen zu "großzügig", denn das wären
    alle struct - z. B. auch System.Collections.Generic.List<T>+Enumerator<T>
    (jetzt unmotiviert aus der langen Liste von ValueType rausgegriffen).

    Zum Schluß: IMHO haben solche Dinge im Anwendungscode nichts verloren.
    Das solltest Du auf unterster Ebene im Framework regeln, sonst kämpfst Du später immer wieder damit!

    Gestern gelesen und passt hier:
    http://ayende.com/Blog/archive/2010/09/09/maintainability-code-size-amp-code-complexity.aspx
    Als Contributor und Initiator von OSS Projekten kann Ayende das gut einschätzen;
    siehe auch die Kommentare und Links dort.

    Gruß Elmar
    Samstag, 11. September 2010 07:45
    Beantworter
  • Hi Elmar,

    > Mir fehlt bei Deinem Beispiel - wie auch Thomas - der nähere Verwendungszweck,
    > oben deutest Du nur Reflektion + Emit an. Der Vergleich alleine hat wenig Aussagekraft.

    Ok also, ich will in meinem Persistenframework dem Benutzer die Möglichkeit geben, eine Menge von Objekten in einer Datenquelle zu suchen. Dazu braucht er nur ein Entitätenobjekt zu erzeugen und die gewünschten Werte in den gewünschten Properties zu hinterlegen. Die direkte, abstrakte Basisklasse der Enitäten "DbPersistable" (für Entitäten aus einer Datenbank)  enthält eine statische Methode die die vom Benutzer übergebene(n) Entität/en in eine Query umwandeln soll.

    Dazu habe ich definiert dass bei Zahlenwerten die "0" bzw. "null" bei Nullable Types/Referenzen,DateTime.Min bei DateTime und Guid.Empty bei Guid etc. als nicht gesetzt angesehen wird.

    Nun bekommt die Methode eine Entität, über deren Aufbau die Klasse nur das weiss, wass sie selbst an die Entitäten vererbt. Sie weiss also nicht welche Eigenschaften eine Entität enthält. Somit bleibt nur der Weg über Reflektion. Nun lese ich die öffentlichen Properties der Enittät aus. Dazu vererbt DbPersistable eine bereits implementierte, aber virtuelle Methode "GetColumns", die alle öffentlichen Properties (via Reflektion) als IDictionary<string,object> (also Spaltenname, Spaltenwert) zurückgibt die mit einem PersistenceElementAttribute als Datenbankspalte gekennzeichnet sind.

    Hier mal ein vereinfachtes Beispiel:

    var benutzer = new Benutzer(); //Entität erzeugen
    benutzer.Nachname = "Rößler"; //Nachname ist Suchkriterium
    
    //Aufruf der Suchmethode 
    DbPersistable.FindEntites(benutzer); //Alle Benutzer mit dem Nachnamen "Rößler" suchen
    
    public static IList<T> FindEntites<T>(T entity) where T : DbPersistable {
     ...
     var columns = entity.GetColumns();
     foreach(var column in columns) {
      //Hier wird gepüft welche Felder gesetzt sind...
     }
    }
    
    

    Ich hoffe der "Anwendungfall" ist nun klarer. :o)

    Vielen Dank und viele Grüße

    Holger M. Rößler

    Samstag, 11. September 2010 09:27
  • Hallo Holger,

    dass kann man sich deutlich erleichtern, in dem man Zugriffsklassen
    für Felder oder Eigenschaften als Proxys verwendet.

    Einer der typischen Ansätze (findet man u. a. in diversen ORMs)
    ist der Weg über DynamicMethod . Damit legt man einmalig den Typ fest
    und Prüfungen erfolgen über die -  idR. generische - Zugriffsklasse.
    Da dann der Typ dauerhaft feststeht, sind Vergleichsorgien nicht mehr notwendig.
    Und DynamicMethod ist so nebenbei um einiges schneller als Reflection pur.

    Ein Artikel, mit dem ich meinen - wenn auch eher seltenen genutzten -
    Builder gestrickt hatte:  Dynamic Code Generation vs Reflection
    Bei Codeproject findest Du weitere, teilweise aktuellere.
    Und bei den ORMs ausgereiftere geprüfte, z. B. NHibernate.

    Eine Basisklasse wäre dazu nicht erforderlich. Das können Attribute
    oder externe Definitionen (XML Mapping bei NHibernate, EF uam) erledigen.

    Gruß Elmar

    Samstag, 11. September 2010 10:27
    Beantworter
  • Hallo Elmar,

    mal wieder vielen Dank für deine Tipps! :o)

    Wie so oft, kommt man nicht auf die einfachste Lösung! Das ich nicht gleich daran gedacht habe!!!! System.Type hat eine nette Property FullName, die den Name des Typen zurückgibt. Somit spare ich mir die Typenvergleiche und vergleiche einfach die strings, was deutlich flotter gehen dürfte.

    Hier mal der Code:

    public static class TypeExpander {
    
    .
    .
    .
    
    private static readonly IList<string> TYPE_FULL_NAME = new 
    List<string>() {
     typeof(byte).FullName, typeof(short).FullName, typeof(ushort).FullName, typeof(int).FullName, typeof(uint).FullName, 
    
     typeof(long).FullName, typeof(ulong).FullName, typeof(BigInteger).FullName, typeof(float).FullName, typeof(double).FullName, 
    
     typeof(decimal).FullName 
    };
    
    .
    .
    .
    
    public static bool IsNumeric(this Type type) {
     if(type == null) {
     return false;
     }
     return TypeExpander.TYPE_FULL_NAME.Any(t => t == type.FullName);
    }
    

    Ich werde das mal ausprobieren, und gebe bescheid ob das so passt.

    Vielen Dank und viele Grüße

    Holger M. Rößelr

    Samstag, 11. September 2010 20:41
  • Hallo Holger,

    dann würde ich das schon eher so machen (ungetestet):

      public static bool IsNumericType(Type type)
    	{
    		if (type == null)
    			return false;
    
    		switch (Type.GetTypeCode(type))
    		{
    			case TypeCode.SByte: 
    			case TypeCode.Byte: 
    			case TypeCode.Int16: 
    			case TypeCode.UInt16: 
    			case TypeCode.Int32: 
    			case TypeCode.UInt32:
    			case TypeCode.Int64: 
    			case TypeCode.UInt64: 
    			case TypeCode.Single: 
    			case TypeCode.Double: 
    			case TypeCode.Decimal:
    				return true;
    				
    			case TypeCode.String: 
    			case TypeCode.Char: 
    			case TypeCode.Boolean: 
    			case TypeCode.DateTime: 
    			case TypeCode.DBNull: 
    			default:
    				return false;
    		}
    	}
    

    denn switch wird hier vom C# Compiler über eine Sprungtabelle realisiert, was sehr fix ist.
    Hast Du Nullable-Types käme da noch etwas mehr hinzu.

    Gruß Elmar

    Samstag, 11. September 2010 20:55
    Beantworter
  • Hallo Elmar,

    nochmals vielen Dank für deine Mühe!

    So werde ich es machen. Das gefällt mir deutlich besser, als dieses Listendurchitriererei die ich momentan betreibe.

     

    Nochmals vielen Dank an alle, die sich an meinem Problem den Kopf zerbrochen haben :o)

    Vielen Dank und Viele Grüße

    Holger M. Rößler

    Sonntag, 12. September 2010 08:01