Funkcje statyczne w typach generycznych

0

Potrzebuję przesyłać przez sieć dane o ściśle określonej strukturze, więc wymyśliłem sposób, żeby je przekonwertować do tablicy bajtów i z powrotem: z tablicy bajtów do obiektu. Przykładowy obiekt wygląda tak:

public class Message : ConvertableToBytes<Message>
{
    public Message() {}

    public Message(string text)
    {
        Text = text;
    }
    
    public string Text { get; }
}

Obiekt ten dziedziczy po klasie abstrakcyjnej, która implementuje m.in. dwie metody:

[Serializable]
public abstract class ConvertableToBytes<T> where T : new()
{
  public byte[] PackToBytes()
  {
    // [...]
  }
  
  public static T Unpack(byte[] encodedData)
  {
    T result = new T();
    // [...]
  }
}

Jak się można domyślić, jedna pakuje obiekt w bajty, druga z bajtów wyciąga obiekt.

Działało to dobrze dopóki nie zacząłem bawić się w typy generyczne. Otóż chciałem zrobić taką metodę, która umożliwia wysłanie wiadomości i jednocześnie oczekiwanie na odpowiedź określonego typu T. Metody SendMessageAwaitResponse nie powinno interesować czym jest T, ważne jedynie żeby T miało funkcję Unpack, która odpakowałaby odpowiedź wracającą z sieci.

public async Task<T> SendMessageAwaitResponse<T>(MessageNetworkLayer message)
{
   ResponseStatus status = await SendMessage(message);

   T response = T.Unpack(status.ImmadiateResponseBytes);

   return response;
}

Normalnie w takich przypadkach zrobiłbym po prostu interfejs, dajmy na to IConvertableToBytes i kazałbym mu implementować Unpack, a w funkcji SendMessageAwaitResponse określiłbym, że T to IConvertableToBytes. Tylko problem w tym, że Unpack jest statyczne, a takich funkcji do interfejsu wrzucić się nie da.

Rozwiązaniem byłoby też zmienienie Unpack na funkcję niestatyczną, ale nie podoba mi się ta opcja, bo wymuszałaby na programiście utworzenie ręcznie instancji rozpakowywanego obiektu i przekazanie go jako argument do Unpack. Trochę niewygodne, więc zostawiam to jako ostateczność.

Ma ktoś pomysł jak by to rozwiązać?

5

Czemu Message musi umieć się konwertować do byte[]? Niech jest tym czym jest - wiadomością. Jak chcesz ją konwertować do byte[] to zrób inną klasę, która będzie się tym zajmowała. ToBytesConverter czy cokolwiek. Single responsibility.

1

Tak jak napisał @WyjmijKija, lepiej rozdzielić te odpowiedzialności na dwie klasy. Nie ma potrzeby, żeby klasa wiadomości zajmowała się serializacją samej siebie do tablicy bajtów. Możesz sobie zrobić interfejs konwertera:

public interface IBytesConverter<T>
{
    byte[] PackToBytes(T data);

    T Unpack(byte[] encodedData);
}

Implementacja tego interfejsu dla konwertera wiadomości będzie wtedy wyglądać tak:

public class MessageToBytesConverter : IBytesConverter<Message>
{
    public byte[] PackToBytes(Message data)
    {
        throw new NotImplementedException();
    }

    public Message Unpack(byte[] encodedData)
    {
        throw new NotImplementedException();
    }
}

Nie wiem w jakiej klasie masz metodę SendMessageAwaitResponse, ale trzeba do niej dodać słownik typ wiadomości -> instancja konwertera.

public class MessageDispatcher
{
    private readonly Dictionary<Type, object> _converters = new();

    public MessageDispatcher AddConverter<T, TConverter>() where TConverter : new()
    {
        _converters.Add(typeof(T), new TConverter());
        
        return this;
    }
    
    public async Task<T> SendMessageAwaitResponse<T>(MessageNetworkLayer message)
    {
        ResponseStatus status = await SendMessage(message);
        
        var converter = (IBytesConverter<T>)_converters[typeof(T)];

        var response = converter.Unpack(status.ImmadiateResponseBytes);

        return response;
    }
}

Wtedy przypadek użycia:

var dispacher = new MessageDispatcher()
    .AddConverter<Message, MessageToBytesConverter>();
    
var responseMessage = await dispatcher.SendMessageAwaitResponse<Message>(new MessageNetworkLayer());
0

a jak chcesz brzydko to mozesz lekko zmodyfikowac metode (albo stworzyc nowa, ktora wywola tą nie-do-zmiany)

public async Task<T> SendMessageAwaitResponse<T>(MessageNetworkLayer message) where T : new()
      {
           return ConvertableToBytes<T>.unpack(new byte[0]);
      }
0

Tylko problem w tym, że Unpack jest statyczne, a takich funkcji do interfejsu wrzucić się nie da.

hehe

public interface ITest
{
	static void Test()
	{
	}
}

ale to akurat trochę inaczej działa :P

0

Zrób tak jak radzi @WyjmijKija, pomimo tego że wywołanie statica z generycznym parametrem nie jest żadnym problemem:

public async Task<T> SendMessageAwaitResponse<T>(MessageNetworkLayer message) where T : new()
{
    ResponseStatus status = await SendMessage(message);

    T response = ConvertableToBytes<T>.Unpack(status.ImmadiateResponseBytes);

    return response;
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1