Generische Methode mit Funktionsüberladung



  • Hallo,

    ich versuche gerade etwas C++ templateähnliches in C# umzusetzen. Ich hab´ verstanden, dass C# Generics was komplett anderes als C++ Templates sind und dass mein Ansatz möglicherweise nicht lösbar ist.
    Also, ich habe verschiedene Datentypen, die per TCP/IP verschickt werden. Zusätzlich möchte ich Listen dieser Objekte verschicken, aber ich möchte nicht für jeden Objekttypen eine eigene Überladung der Write Methode implementieren müssen.
    So sieht das bisher aus:

    class TelegramWriter
    {
       ...
       public void Write( ObjectType1 obj )
       {
          // member schreiben (Primitive Datentypen + string)
       }
    
       public void Write( ObjectType2 obj )
       {
          // member schreiben (Primitive Datentypen + string)
       }
    
       public void Write( List<ObjectType1> objects )
       {
          Write( (UInt32) objects.Count );
          foreach( ObjectType1 obj in objects )
          {
             Write( obj );
          }
       }
    
       public void Write( List<ObjectType2> objects )
       {
          Write( (UInt32) objects.Count );
          foreach( ObjectType2 obj in objects )
          {
             Write( obj );
          }
       }
    
       // "Lösung" mit Generics funtkioniert nicht, da der Compiler den konkreten Datentypen
       // nicht erkennt und daher nicht weiß, welche Überladung er aufrufen muss.
       public Write<T>( List<T> objects ) where T : class
       {
          Write( (UInt32) objects.Count );
          foreach( T obj in Objects )
          {
             Write( obj ); // Fehlermeldung: CS1503: "Konvertierung von "T" in "ObjectType1" nicht möglich
          }
       }
    }
    

    Ich habe auch schon versucht, eine Interfaceklasse IWriteable zu benutzen, von der meine konkreten Datentypen erben und die als Requirement in die where Klausel einzubauen, führt aber zum gleichen Fehler. Mir fallen spontan zwei Lösungen ein, die ich aber beide nicht gut finde:

    1. alle Klassen erben von einem IWriteable Interface und schreiben ihre Daten selbst.
      class ObjectType1 : IWriteable
      {
         public override void Write( TelegramWriter writer )   
         {
             // schreibe member
         }
      }
    
    1. ich mache in der Write<T> Methode eine Fallunterscheidung nach Typ, caste das Objekt und rufe die passende Überladung auf:
    void Write<T>( List<T> objects ) where T : class
    {
       Write( (UInt32) objects.Count );
       foreach( var obj in objects )
       {
          if( obj is ObjectType1 ) Write( obj as ObjectType1 );
          else if( obj is ObjectType2 ) Write( obj as ObjectType2 );
    
          // Das hier geht komischerweise auch nicht:
          // Fehlermeldung: CS0030: Der Typ "T" kann nicht in "ObjectType1" konvertiert werden
          // if( obj is ObjectType1 ) Write( (ObjectType1) obj );
       }
    }
    

    Wie sähe hier eine vernünftige Lösung aus?



  • Ich denke Lösung 1 wird wohl oft das beste sein.

    Du willst üblicherweise nicht dass dir der Code bei einer fehlenden Serialisierungsfunktion mit einem Laufzeitfehler um die Ohren fliegt.

    Alternativ kann ich nur das anbieten:

    using System;
    using System.Collections.Generic;
    
    public class Program
    {
    	public static void Main()
    	{
    		var f = new Foo();
    		var b = new Bar();
    		var fs = new List<Foo>();
    		fs.Add(new Foo());
    		fs.Add(new Foo());
    		fs.Add(new Foo());
    
    		Ser.Write(f);
    		Ser.Write(b);
    		Ser.Write(fs, e => Ser.Write(e));
    	}
    }
    
    public class Foo
    {
    }
    
    public class Bar
    {
    }
    
    public class Ser
    {
    	public static void Write(Foo f)
    	{
    		Console.WriteLine("foo");
    	}
    
    	public static void Write(Bar b)
    	{
    		Console.WriteLine("bar");
    	}
    
    	public delegate void WriteFunction<T>(T t);
    	public static void Write<T>(IList<T> list, WriteFunction<T> wf)
    	{
    		Console.WriteLine(list.Count + " elements:");
    		foreach (T e in list)
    			wf(e);
    	}
    }
    


  • Danke für deine Antwort, Hustbär. Ich bin inzwischen auch bei Lösung 1 angekommen, auch wenn ich mich mit dem Gedanken iwie nicht anfreunden kann, dass sich die Objekte selbst per TCP/IP verschicken.
    Aber irgendeinen Tod muss man ja sterben 😉



  • @DocShoe sagte in Generische Methode mit Funktionsüberladung:

    auch wenn ich mich mit dem Gedanken iwie nicht anfreunden kann, dass sich die Objekte selbst per TCP/IP verschicken.

    Tun sie ja nicht. Sie bieten lediglich selbst Support für die nötige (De)Serialisierung an.

    Die Variante mit Ser.Write(fs, e => Ser.Write(e)); finde ich aber ehrlich gesagt auch nicht so schlimm. Es gäbe da IIRC sogar Magic mit der man zumindest zur Laufzeit sicherstellen kann dass das was übergeben wird auch wirklich ein Delegate ist der Ser.Write aufruft. Müsste ich aber erst selbst noch googeln wie das genau geht. Hat mit der schwarzen Magie zu tun die für Linq gebaut wurde.



  • Hab noch etwas gegoogelt. Um die schwarze Magie zu verwenden müsstest du hier Expression<Action<T>> als Parameter nehmen statt des Delegates. Das blöde dabei ist nur: um das Ding dann auszuführen müsstest du es runtime compilieren. Was zu Garbage und schlechter Performance führt.

    => Vermutlich nicht so gut.

    Bessere Variante kenne ich nicht. Heisst aber nicht dass es keine gibt, ich bin was C# angeht nicht mehr wirklich auf dem Laufenden.


Anmelden zum Antworten