Partage
  • Partager sur Facebook
  • Partager sur Twitter

[C#] Conversion de type avec generique et heritage

Sujet résolu
    10 novembre 2019 à 21:58:14

    Bonsoir,

    Je vous présente le problème auquel je fais fasse depuis 45 min, je viens vers vous car je ne vois pas du tout comment faire.Le problème vient d'une NullReferenceException, due au fait que lorsque je convertit mon objet DofusClient en TClient via l'opérateur as qui me retourne manifestement null.  Je ne vois pas pourquoi le compilateur me refuse ce transtypage alors que TClient lors de l'appel de ma class est un objet du type AuthClient qui hérite de DofusClient, puis je l'ai également rajouté dans la clause where de ma class Frame Manager...

    Voici les Class en question : 

     public class FrameManager<TClient> : IFrameManager where TClient : DofusClient
        {
            private readonly ImmutableDictionary<int, Action<TClient, IDofusMessage>> m_frames;
            private IContextHandler m_threading;
    
            public FrameManager(ImmutableDictionary<int, Action<TClient, IDofusMessage>> frames,
                IContextHandler threading)
            {
                this.m_frames = frames;
                this.m_threading = threading;
            }
    
            public void Execute<TMessage>(DofusClient client, TMessage message) where TMessage : IDofusMessage
            {
                if (!m_frames.TryGetValue(message.ProtocolId, out var method))
                    Logger.Instance.Log(LogLevel.Warn, "NETWORK-FRAME", $"Message id {message.ProtocolId} exists but isn't listed on frames.");
                else
                {
                    Logger.Instance.Log(LogLevel.Info, "NETWORK-FRAME", $"Message Id {message.ProtocolId} has been parsed and treated.");
                   
                    m_threading.ExecuteInContext(() => method(client as TClient, message));
                }
            }
        }


    Voici la manière dont je crée mes delegate : 

     public class FrameBuilder<TClient>
        {
            private readonly ImmutableDictionary<int, Action<TClient, IDofusMessage>>.Builder m_frames;
    
            public FrameBuilder()
            { this.m_frames = ImmutableDictionary.CreateBuilder<int, Action<TClient, IDofusMessage>>(); }
    
    
            public FrameBuilder<TClient> RegisterFrame<TFrame>() where TFrame : IFrame, new()
            {
                IFrame frame = new TFrame();
                Action<TClient, IDofusMessage> del;
                MessageHandlerAttribute attribute;
                IEnumerable<MethodInfo> methods = frame.GetType()
                                                        .GetMethods()
                                                        .Where(x => (x.GetCustomAttribute(typeof(MessageHandlerAttribute))
                                                        as MessageHandlerAttribute) != null);
                foreach (MethodInfo method in methods)
                {
                    attribute = (MessageHandlerAttribute)method.GetCustomAttribute(typeof(MessageHandlerAttribute));
    
                    del = Delegate.CreateDelegate(typeof(Action<TClient, IDofusMessage>), null, method)
                          as Action<TClient, IDofusMessage>;
    
                    m_frames.Add(attribute.MessageId, del);
    
                }
    
                Logger.Instance.Log(LogLevel.Debug, $"FRAME-BUILDER",
                    $"{ methods.Count()} methods handled from {typeof(TFrame).Name}.");
    
                return this;
            }
    
            public ImmutableDictionary<int, Action<TClient, IDofusMessage>> Build()
            { return m_frames.ToImmutable(); }
        }

    Voici la manière dont est utilisé FrameManager : 

     public class FrameDispatcher
        {
            private readonly ImmutableDictionary<int, Func<IDofusMessage>> m_messageProvider;
            private readonly IFrameManager m_frameManager;
    
            public FrameDispatcher(ImmutableDictionary<int, Func<IDofusMessage>> messageProvider, IFrameManager frameManager)
            {
                this.m_messageProvider = messageProvider;
                this.m_frameManager = frameManager;
            }
    
            public void Dispatch(DofusClient client, Frame<DofusMetadata> frame)
            {
                int key = frame.Metadata.MessageId;
    
                if (!m_messageProvider.TryGetValue(key, out var msgCtor))
                    Logger.Instance.Log(LogLevel.Warn,"[NETWORK]", $"The message received isn't in the protocol : {key}.");
                else
                {
                    IDofusMessage message = msgCtor();
                    var reader = new DofusReader(frame.Payload);
    
                    message.Deserialize(reader);
                    m_frameManager.Execute(client, message);
                }
    
            }
        }



    AuthClient.cs : 

     public class AuthClient : DofusClient
        {
            public AuthClient() : base() { }
        }

    Bien sûr il serait plus facile de mettre directement le type AuthClient dans Frame Builder.cs et Frame Manager.Cs mais le projet est séparé en plusieurs assemblys où AuthClient n'est pas dans l'assembly des autres classe. De plus FrameBuilder/FrameManager seront réutilisé pour un autre type de données donc je ne préfère pas me répéter dans le code :D

    Merci d'avoir pris le temps de lire mon problème et bonne soirée.

    • Partager sur Facebook
    • Partager sur Twitter
      12 novembre 2019 à 11:49:14

      J'ai l'impression que vous avez un peu négligé la phase de conception de vos classes.

      > Je ne vois pas pourquoi le compilateur me refuse ce transtypage

      C'est pas le compilateur qui refuse le transtypage, on n'est à l'exécution pas à compilation.

      Celui qui vous envoie balader, c'est le runtime parce que vous essayez d'utiliser une référence égale à null.

      Votre "as" ligne 21 n'est pas "safe".

      Votre condition sur le paramètre de la classe générique fait que "TClient" doit être "DofusClient" ou une classe fille.

      La signature de la méthode "Execute" fait que "client" est un "DofusClient" ou une instance d'une classe fille.

      Mais qu'est-ce qui garantit que "client" soit un TClient ??? Rien.

      Vous nous indiquez même pas la valeur de "TClient" quand le runtime vous envoie bouler.

      • Partager sur Facebook
      • Partager sur Twitter
      Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
        14 novembre 2019 à 11:50:01

        En réalité l'erreur était due au fait que je faisais un down casting impossible du coup j'ai ré-architecturé mon code et je suis passé par IoC(injection dépendance) au lieu de l'héritage ( AuthClient hérite plus de Dofus Client, AuthClient possède une propriété INetworkClient)

        -
        Edité par Perosuperō 14 novembre 2019 à 11:51:26

        • Partager sur Facebook
        • Partager sur Twitter
          14 novembre 2019 à 13:58:24

          J'approuve !!! ;)
          • Partager sur Facebook
          • Partager sur Twitter
          Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.

          [C#] Conversion de type avec generique et heritage

          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
          • Editeur
          • Markdown