C# jak zapełnić listę obiektami dziedziczącymi generyczny interfejs


#1

witam,
posiadam interface:

public interface FieldSerializer<T>
{
    void Serialize( T obj);
    T Deserialize();

}

chciałbym potem wsadzić to w liste ( w java wyglądało by to tak List<FieldSerializer<?>>) ale c# nie obsługuje typów ogólnych
a liste uzupełnić masą różnych obiektów dziedziczących interfejs

próbowałem tak:

public interface FieldSerializer
{
    void Serialize(JsonWriter writter, object obj);
    object Deserialize(JsonReader reader);

}

public interface FieldSerializer<T> : FieldSerializer
{
    new void Serialize(JsonWriter writter, T obj);
    new T Deserialize(JsonReader reader);
}

problem z tym rozwiązaniem jest takie że klasa która dziedziczy po FieldSerializer wymaga zaimplementowania 4 metod ,
da sie te dwie metody jakoś przesłonić ?


#2


Tutaj troche podpowiadają, ale wg mnie lepiej było by to zrobić, gdyby wprowadzić kolejny interfejs, np ISerializable, wtedy serializer wygladał by tak:

public interface FieldSerializer
{
    void Serialize(JsonWriter writter, ISerializable obj);
    ISerializable Deserialize(JsonReader reader);
}

#3

Troszkę kombinujesz jak koń pod górę. Typ generyczny bardzo słabo sprawuje się jako interfejs.

Czemu po prostu nie dasz object? Przeciez w C# wszystko wywodzi sie z tej klasy.

Public interface FieldSerializer
{
object Serialize( object obj );
object Deserialize();
}

Ale jak sie upierasz to pamiętaj ze Listy w C# oczekuja konkretnego typu danych, nie mozesz wiec wstawic T, bo to nie obiekt. Moglbys np wstawic List<FieldSerializer> o ile dobrze pamietam.

Ale jezeli nadal sie upierasz to musisz sie pozbyc typu generycznego i dobrze kombinowales ale zapomniales ze C# daje opcje stworzenia pustego interfejsu. A wiec wywal te dwie metody z poczatkowego i odziedzicz

( Edit: nawet nie zauwazylem ze kolega wyzwj juz podal rozwiazanie z pustym interfejsem… niepotrzebnie sie wysilalem :wink: )


#4

Wydaje mi sie ze tez przy serializacji powinienes zwracac obiekt a nie tylko przy deserializacji, ale moze po prostu za pozno jest i juz nie mysle trzezwo


#5

cała idea polega na tym aby klasa dziedzicząca po interfejsie miała łatwy sposób na implementację zawartych w niej metod, gdy zrobie to na Object będę zmuszony rzutować wartości (sprawdzanie typów odbywa się przed wywołaniem metody interfejsu dla serializacji i po deserializacji),

public class IntSerializer : FieldSerializer<int>
public class SomeClassSerializer : FieldSerializer<SomeClass>

mógłbym dać jako parametr zamiast T Object ale to słabo to wygląda i jest mało wygodne

  • serializacja nic nie zwraca bo obiekt jest w niej zapisywany za pomocą JsonWriter, obie metody nie zwracają informacji o tym czy się udało ale jest to (przynajmniej na tym etapie) zbędne jeśli jest jakiś błąd wywala całą serializacje/deserializacje

myślałem że można coś pokombinować z parametrem new aby przesłonić metody, ale nie wyszło.
trochę lipnie że nie mogę zrobić tego tak

public interface FieldSerializer<T>
{
    void Serialize( T obj);
    T Deserialize();
}

i potem 
List<FieldSerializer<object>> 
problem z tym rozwiązaniem jest taki że lista nie przyjmie klasy rozszerzającej FieldSerializer<T> z innym parametrem gnerycznym niż object 

jak się nie uda to zrobię to brzydko o tak:

public interface FieldSerializer 
{
    void Serialize(JsonWriter writter, object obj);
    object Deserialize(JsonReader reader);

}
public abstract class SomeClass<T> : FieldSerializer
{
    public object Deserialize(JsonReader reader)
    {
        return Wczytaj(reader);
    }

    public void Serialize(JsonWriter writter, object obj)
    {
        Zapisz(writter, (T)obj);
    }


    public abstract void Zapisz(JsonWriter w, T o);
    public abstract T Wczytaj(JsonReader reader);
}

#6

Zasadnicze pytanie jest takie: po co ci lista obiektów z interfejsem do serializacji?
Nie podałeś też środowiska w jakim piszesz, więc pojadę uniwersalnym rozwiązaniem.
Jeśli chcesz zaserializować jakiś obiekt(zwłaszcza do JSON`a) to polecam jakieś gotowe rozwiązanie np.
https://www.newtonsoft.com/json/help/html/SerializingJSON.htm

Ogólnie w 80% przypadkach nie potrzebujesz pisać customowych serializerów/deserializerów a ten sposób który przedstawiłeś wykorzystywany jest w sytuacji gdy chcesz napisać specyficzny obiekt albo uzyskać zgodność z jakąś starszą wersją modelu(np. stare save), choć chyba najpowszechniej jest wykorzystywany w Networkingu do przesyłania informacji o obiektach pomiędzy klientami.


#7

to był tylko przykład z tą listą… ale dla problemu jest to bez znaczenia

chce zrobić własny serializer który automatycznie na podstawie adnotacji i pol klasy będzie serializował/deserializował klasy, na początku to pewnie będą dwie mapy z id klasy oraz jesli nie posiada to z nazwą, obie mapy będą przechowywać klasy dziedziczone po interfejsie. wszystko po to aby zrobić mechanizm zapisu uwzględniający polimorfizm

chciałbym też zapisywać wartości prymitywne w postaci tablicy bitów


#8

Ok, więc jeśli chcesz zrobić własny serializer, to zacznij od analizy innych serializerów, jak one działają i jak udostępniają te opcje dla innych. Aktualnie najpopularniejsze rozwiązania wykorzystują refleksję, i ogólnie tworzenie list dla interfejsu Serializable jest trochę bez sensu, bo z perspektywy pisania oprogramowania zazwyczaj sięgasz po serializację dopiero jak masz logikę napisaną pod to, ale zakładając że chcesz do swojego problemu znaleźć jakieś zastosowanie biznesowe, to może być to wysokowydajny streamowy loader danych, w takim przypadku możesz sobie stworzyć dla obiektu który chcesz zserializować np. House, odpowiedni konwenter np. HouseConventer który implementuje np. ObjectSerializer< House >, dla typów prostych tworzysz np. IntegerConventer : TypeSerializer/ObjectSerialize< int > , potem tworzysz JsonSerializer w którym tworzysz albo przekazujesz twoje Conventery i potem dajesz opcje do Serialize, Deserialize na bazie zadanego typu. I on sobie szuka odpowiedniego Conventera dla wrzuconego obiektu, jak znajdzie to wywołuje odpowiednią metodę, teoretycznie w tej metodzie przekazujesz wszystkie pola które chcesz zaserializować z danego obiektu znowu do Serializera, a potem możesz je odserializować w podobny sposób. Tylko że robienie tego czegoś dla JSON`a nie ma sensu, lepiej zrobić binarny zapis, bo finalnie takie stream będzie się składał z typów prostych, ustawionych w odpowiedniej kolejności. Zapis listy możesz sobie zrobić z dodatkowym polem na jej rozmiar, a reszta będzie analogiczna, każdy element do Conventera a potem kolejne zmienne.


#9

wersja to chyba będzie to 7.3
więc wychodzi że docelowo na platformę Standard 2.0

no robię to na miej więcej taktylko do json’a,
całkowicie binarny zapis jest mi na ten moment niepotrzebny, bardziej zależy mi na łatwej implementacji i późniejszej modyfikacji, json to daje w tym możliwość wkładania czegokolwiek jako dane i to jest mój cel.
:confused:

dobra przykład mam pola:

private int a;
string c;
SomeObjectA e;

i program będzie mi to serializował wszystkie pola do pliku, w tym pole z obiektem SomeObjectA: (pominę fakt że obiekt jest serializowany tak jak jest a więc nie są zachowane żadne referencje),
dodatkowo obiekt który jest serializowany nie musi być dokładnie takiego typu jak został zadeklarowany może on być rozszerzeniem typu pola . i ja sobie to zrobię…

problem jaki mam to że nie potrafię upchać typu ogolnego w jakąkolwiek kolekcję generyczną, baa nawet pole wywala bląd rzutowania przy próbie ustawienia obiektu
interface IInter
Inter field = SomeClassExtendingInter();


#10

Musiałbyś pokazać mi kod, bo nie rozumiem w czym masz problem, np:
Inter field = SomeClassExtendingInter();
wygląda bardziej jakbyś chciał utworzyć obiekt:
Inter field = new SomeClassExtendingInter();
bo jak chcesz wywołać metodę która zwraca obiekt to lepiej daj nazwę CreateObjectExtendingInter


#11

#12

Nie możesz tak zrobić, ponieważ:

   /*
    [InitializeOnLoadMethod]
    */
    private static void init()
    {
        FieldSerializer<object> ser;
        FieldSerializer<int> sir;
        //niewłaściwe rzutowanie typu 
        ser =  new ObjectSerializer();
        sir =  new IntSerializer();

        object anyObject = "String to też obiekt";
        ser.Serialize(new JsonWriter(), "String to też obiekt");
        sir.Serialize(new JsonWriter(), "ale nie int :D");
    }
}

czyli gdybyś nawet opakował FieldSerializer< int > do FieldSerializer< object > to implementacyjnie ciągle byłby FieldSerializer< int > ale mógłbyś mu przekazywać do metody Serialize dowolny obiekt, np. float, HashMap itp. więc kompilator ci na to nie pozwala, tak więc typ obiektu generycznego powiązany jest także z typem/typami generycznymi które dana klasa implementuje. W Javie jest to możliwe do osiągnięcia, ale wynika z wstecznej kompatybilności gdzie tych typów nie było i właśnie może powodować takie dziwne rzeczy


#13

nooo doszedłem do tego że kompilator wywali mi błąd i c# nie posiada operatora ogólnego typu ‘?’.
Kod będzie sprawdzał typ na rzecz którego będzie wywoływany interfejs więc nie powinno nigdy dojść do pomyłki.
ogólnie rzecz ujmując zrobię to trochę brzydko tak jak napisałem w poście powyżej tworząc interfejs bez typu generycznego a metody będą przyjmować /zwracać object.

potem w klasie abstrakcyjnej z typem generycznym zaimplementuje interfejs i dodam do niego dwie metody abstrakcyjne które będą wywoływane z funkcji interfejsu <- ale takie rozwiązanie jest brzydkie, a chciałbym się też czegoś nauczyć a nie tylko szukać objeść do problemu.


#14

Polecam zastosować klasyczne podejście:

  1. Zdefiniuj co chcesz osiągnąć, potem wyrzuć z tego rzeczy które są niepotrzebne i utwórz wersja najbardziej minimalną.
  2. Zobacz czy ktoś już nie zrobił tego. Jeśli zrobił to przeanalizuj jak to tam działa.
  3. Zrób żeby działało.
  4. Poprawiaj(refaktoryzuj) ile chcesz :smiley: tylko co jakiś czas sprawdzaj czy nadal działa :slight_smile:

#15

Hej,

Najprościej zrobić jak opisał Radomiej, używając Newtonsoft (ewentualnie do XMLa - .net ma to w standardzie). Jeśli się nie mylę standardowo wszystkie publiczne ‘propertisy’ są serializowane.

W obu podejsciach można sobie dopisać metody pomocnicze Load/Save (kod bez error-handlingu!), w Newtonsoft to coś jak:

public void Save<T>(T entity, string filePath)
{
    JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
    var json = JsonConvert.SerializeObject(entity, settings);
    File.WriteAllText(filePath, json);

}
    
public T Load<T>(string filePath, Container container) where T: class
{
   var json = File.ReadAllText(filePath);
   JsonSerializerSettings settings = new JsonSerializerSettings 
   { TraceWriter = traceWriter, TypeNameHandling = TypeNameHandling.All };
  T  entity = JsonConvert.DeserializeObject<T>(json, settings);

  return entity;
}

#16

zapis do json zrobiłem ( aktualnie taki prosty aby działał, który zapisuje nazwę klasy serializowanej po czym wszystkie odsłonięte pola z klasami pochodnymi) od razu poduczyłem się jak działa boxing w c# :D.
deserializacja odbywa się bez typu obiektu.

problem z brakiem typu aninomowego rozwiązałem najprościej jak się dało zastępując parametr ogolny T klasa ‘object’ :D, < znam java i myślałem że istnieje coś podobnego do typu anonimowego w c#, albo jakieś eleganckie rozwiązanie, no ale nic na siłę,
teraz staram się zaimplementować algorytm marching cube :smiley: więc temat już nieaktualny