2010-01-21 14 views
8

Pracuję nad formantem, który może przyjmować wiele różnych typów danych (wszystko, co implementuje IComparable).Ogólna konwersja typów bez ryzyka Wyjątki

potrzebuję, aby móc porównać je z innej zmiennej przekazanej w.

Jeśli główny typ danych jest DateTime, a ja przeszedł String, muszę

  • próbę przekonwertować Łańcuch do DateTime, aby wykonać porównanie dat.
  • jeśli ciąg nie może być przekonwertowany na wartość DateTime, wykonaj porównanie ciągów.

Potrzebuję więc ogólnego sposobu, aby spróbować przekonwertować z dowolnego typu na dowolny typ. Łatwo, .Net zapewnia nam klasę TypeConverter.

Teraz najlepsze, co mogę zrobić, aby ustalić, czy ciąg może zostać przekonwertowany na DateTime, to używać wyjątków. Jeśli ConvertFrom zgłasza wyjątek, wiem, że nie mogę wykonać konwersji i muszę porównać ciąg.

Poniżej jest najlepszy mam:

 string theString = "99/12/2009"; 
     DateTime theDate = new DateTime (2009, 11, 1); 

     IComparable obj1 = theString as IComparable; 
     IComparable obj2 = theDate as IComparable; 

     try 
     { 
      TypeConverter converter = TypeDescriptor.GetConverter (obj2.GetType()); 
      if (converter.CanConvertFrom (obj1.GetType())) 
      { 
       Console.WriteLine (obj2.CompareTo (converter.ConvertFrom (obj1))); 
       Console.WriteLine ("Date comparison"); 
      } 
     } 
     catch (FormatException) 
     { 
      Console.WriteLine (obj1.ToString().CompareTo (obj2.ToString())); 
      Console.WriteLine ("String comparison"); 
     } 

Część naszych standardów w stanie roboczym, że:

Wyjątki powinny być podnoszone tylko wtedy, gdy sytuacja Wyjątek - tj. napotkano błąd.

Ale to nie jest sytuacja wyjątkowa. Potrzebuję innego sposobu.

Większość typów zmiennych ma metodę TryParse, która zwraca wartość boolowską, aby umożliwić określenie, czy konwersja się powiodła, czy nie. Ale nie istnieje metoda TryConvert dostępna dla TypeConverter. CanConvertFrom określa tylko, czy możliwa jest konwersja między tymi typami i nie uwzględnia faktycznych danych do przekonwertowania. Metoda IsValid jest również bezużyteczna.

Wszelkie pomysły?

EDIT

Nie mogę korzystać z AS i IS. Nie znam typów danych w czasie kompilacji. Więc nie wiem co do Asa i jest !!!

EDIT

Ok przybity drania. Nie jest tak czysty jak Marc Gravells, ale działa (mam nadzieję). Dzięki za troskę Marc. Będę pracował nad uporządkowaniem go, kiedy znajdę czas, ale mam trochę stosów poprawek, które muszę załatwić.

public static class CleanConverter 
    { 
     /// <summary> 
     /// Stores the cache of all types that can be converted to all types. 
     /// </summary> 
     private static Dictionary<Type, Dictionary<Type, ConversionCache>> _Types = new Dictionary<Type, Dictionary<Type, ConversionCache>>(); 

     /// <summary> 
     /// Try parsing. 
     /// </summary> 
     /// <param name="s"></param> 
     /// <param name="value"></param> 
     /// <returns></returns> 
     public static bool TryParse (IComparable s, ref IComparable value) 
     { 
      // First get the cached conversion method. 
      Dictionary<Type, ConversionCache> type1Cache = null; 
      ConversionCache type2Cache = null; 

      if (!_Types.ContainsKey (s.GetType())) 
      { 
       type1Cache = new Dictionary<Type, ConversionCache>(); 

       _Types.Add (s.GetType(), type1Cache); 
      } 
      else 
      { 
       type1Cache = _Types[s.GetType()]; 
      } 

      if (!type1Cache.ContainsKey (value.GetType())) 
      { 
       // We havent converted this type before, so create a new conversion 
       type2Cache = new ConversionCache (s.GetType(), value.GetType()); 

       // Add to the cache 
       type1Cache.Add (value.GetType(), type2Cache); 
      } 
      else 
      { 
       type2Cache = type1Cache[value.GetType()]; 
      } 

      // Attempt the parse 
      return type2Cache.TryParse (s, ref value); 
     } 

     /// <summary> 
     /// Stores the method to convert from Type1 to Type2 
     /// </summary> 
     internal class ConversionCache 
     { 
      internal bool TryParse (IComparable s, ref IComparable value) 
      { 
       if (this._Method != null) 
       { 
        // Invoke the cached TryParse method. 
        object[] parameters = new object[] { s, value }; 
        bool result = (bool)this._Method.Invoke (null, parameters); 

        if (result) 
         value = parameters[1] as IComparable; 

        return result; 
       } 
       else 
        return false; 

      } 

      private MethodInfo _Method; 
      internal ConversionCache (Type type1, Type type2) 
      { 
       // Use reflection to get the TryParse method from it. 
       this._Method = type2.GetMethod ("TryParse", new Type[] { type1, type2.MakeByRefType() }); 
      } 
     } 
    } 
+0

Niezupełnie, istnieje kwestia wie co typ ma być przekształcony. Tutaj nie. Odpowiedź na to pytanie w ogóle mi nie pomaga. –

+0

Dobrze. Słusznie. – jason

+0

Uff ... Myślałem, że mam zamiar zamknąć moje pytanie. Spędziłem wiele godzin na tym .. ;-) –

Odpowiedz

10

Czy generics jest opcją? Oto bezczelny hack, który poluje metodę TryParse i wzywa go poprzez (kopia) delegata:

using System; 
using System.Reflection; 

static class Program 
{ 
    static void Main() 
    { 
     int i; float f; decimal d; 
     if (Test.TryParse("123", out i)) { 
      Console.WriteLine(i); 
     } 
     if (Test.TryParse("123.45", out f)) { 
      Console.WriteLine(f); 
     } 
     if (Test.TryParse("123.4567", out d)) { 
      Console.WriteLine(d); 
     } 
    } 
} 
public static class Test 
{ 
    public static bool TryParse<T>(string s, out T value) { 
     return Cache<T>.TryParse(s, out value); 
    } 
    internal static class Cache<T> { 
     public static bool TryParse(string s, out T value) 
     { 
      return func(s, out value); 
     }  
     delegate bool TryPattern(string s, out T value); 
     private static readonly TryPattern func; 
     static Cache() 
     { 
      MethodInfo method = typeof(T).GetMethod(
       "TryParse", new Type[] { typeof(string), typeof(T).MakeByRefType() }); 
      if (method == null) { 
       if (typeof(T) == typeof(string)) 
        func = delegate(string x, out T y) { y = (T)(object)x; return true; }; 
       else 
        func = delegate(string x, out T y) { y = default(T); return false; }; 
      } else { 
       func = (TryPattern) Delegate.CreateDelegate(typeof(TryPattern),method); 
      }    
     } 
    } 
} 
+0

Tak, wow, który wygląda na bilet! Dzięki. Będą musieli poczekać do poniedziałku, aby wypróbować go w pracy. Lubię to! –

+0

Bummer zaprzeczył .. Rozwiązanie wyglądało dobrze, ale dane są mi przekazywane tylko jako obiekty. Nie znam typów w czasie kompilacji. Zdaję sobie sprawę, że nie sformułowałem mojego pytania bardzo dobrze, stąd zamieszanie. To jest jednak blisko .. bardzo bardzo blisko, wciąż mogę użyć części refleksyjnej Im jestem pewien. –

+0

Woohoo! Przekręciłem go trochę, aby wymyślić rozwiązanie. Zobacz edycję w moim poście. Wielkie dzięki! –

0

Więc muszę ogólny sposób, aby próbować przekonwertować z dowolnego typu do dowolnego typu. Łatwo, .Net zapewnia nam klasę TypeConverter.

Prosisz za dużo.

class Animal { } 
class Dog : Animal { } 
class Cat : Animal { } 

powinienem być w stanie konwertować Cat do Dog?

Twój problem będzie o wiele łatwiej rozwiązać, jeśli określisz dokładniej (najlepiej dokładnie), jakie ma być zachowanie tej metody. Tak więc zapisz spodziewane dane wejściowe i to, co chcesz, aby dane wyjściowe były w każdym możliwym przypadku. Wtedy twoja metoda powinna sama się napisać.

Więc teraz mamy specyfikacji:

Jeśli główny typ danych jest DateTime, a ja przeszedł String, muszę

próbując przekształcić String do DateTime wykonać porównanie Date. , jeśli String nie można przekonwertować na DateTime, a następnie wykonać porównanie String.

int CompareTo(DateTime d, object o) { 
    string s = o as string; 
    if(s != null) { 
     DateTime dt; 
     if(dt.TryParse(s, out dt)) { 
      return d.CompareTo(dt); 
     } 
     else { 
      return d.ToString().CompareTo(s); 
     } 
    } 
    throw new InvalidOperationException(); 
} 
+0

Chcę Bool TypeConverter.TryConvert (obiekt inObject, out object ConvertedObjected) method. Trochę jak DateTime.TryParse. Chcę zrobić dokładnie to, co zrobił przykładowy kod, który wysłałem, ale bez podniesienia wyjątku. Naprawdę nie robię tutaj obiektów, tylko podstawowe typy zmiennych: ints, string, DateTimes .. Myślałem, że moje pytanie było dość jasne. –

+0

@Pongus: Nie jest jasne. Jakiego typu próbujesz przekonwertować 'inObject' na? Czy próbujesz przekonwertować na typ "ConvertedObjected" (sic)? – jason

+0

przepraszam. Jest to kontrola siatki. Muszę sortować i filtrować na każdym typie danych, który jest wyrzucany na mnie, o ile jest on porównywalny. –

4

Uważam, że ten kod naprawdę powinien rzucać wyjątki, gdy nie może dowiedzieć się konwersji. Jeśli dwa argumenty przekazane są DateTime.Now i Color.Fuschsia, nie można dokonać żadnego znaczącego porównania między nimi, więc każda zwrócona wartość byłaby błędna. To jest definicja właściwego czasu na wyrzucenie wyjątku.

Jeśli koniecznie chcesz uniknąć wyjątków, nie możesz robić tego, czego chcesz, używając dowolnych typów. Każdy typ ma własne reguły dotyczące wartości, które może analizować, a konwerter nie ma możliwości, aby to stwierdzić z góry. (To znaczy, jak już zauważyłeś, wie, że możesz czasami przekonwertować string na DateTime, ale nie jest zaprojektowany, aby wiedzieć, że "1/1/2010" jest prawidłowym DateTime, podczas gdy "Fred" nie jest .)

+0

W tej sytuacji jest całkowicie uzasadnione, że dane wejściowe to DateTime i Colour. Oczywiście, że ich surowe typy danych nie mogą być porównywane. Właśnie dlatego chcę sprawdzić, czy można je przekonwertować, aby można było dokonać użytecznego porównania. Chcę tylko sprawdzić, nie chcę, żeby to był warunek błędu. –

+0

Więc to, czego chcesz, to tylko metoda sprawdzenia, czy konwersja się powiedzie, bez faktycznej konwersji? Nie jest to możliwe w przypadku typów arbitralnych. 'TypeConverter' jest zaprojektowany do próby konwersji i wyrzucania wyjątków w przypadku awarii. Można znaleźć typowe typy, które znasz, jak sobie z nimi poradzić, takie jak 'DateTime' i' Double' i inne, i wywołać ich metody 'TryParse'. Ale sprawdza się tylko w przypadku typów, które sprawdzasz. – Auraseer

5

Jeśli nie jest to możliwe, aby napisać to bez wyjątków, można wyizolować kod problematyczny przez refactoring go w sposób tak:

public static bool TryConvert<T, U>(T t, out U u) 
{ 
    try 
    { 
     TypeConverter converter = TypeDescriptor.GetConverter(typeof(U)); 
     if (!converter.CanConvertFrom(typeof(T))) 
     { 
      u = default(U); 
      return false; 
     } 
     u = (U)converter.ConvertFrom(t); 
     return true; 
    } 
    catch (Exception e) 
    { 
     if (e.InnerException is FormatException) 
     { 
      u = default(U); 
      return false; 
     } 

     throw; 
    } 
} 

Idealnie powinno być przechodzącą w pustych typów jako wyjście parametr, tak aby wartość null reprezentowała niezdefiniowaną wartość (ponieważ nie mogła wykonać konwersji) zamiast wartości domyślnej (tj. 0 dla int)

Powiązane problemy