0

I'm working in a web service that sends SMS messages using N providers. Each provider receives the message in a different format at his own web service.

In order to create a pattern in my web service, I created an abstract class with many methods to be implemented for each of the providers.

Some of the methods need to receive an object that can be of different types, depending on the child class (provider) that inherits it. One of these methods returns an object that can be of at least two different types.

I achieved this working with the "object" keyword, which allows me to pass or return any object:

public abstract class Provider {
    protected HttpClient _Client;
    protected SMSManagerAPIContext _Context;

    public abstract DeliveryResponse SendSMS(object Sms);
    protected abstract DeliveryResponse ParseResponse(HttpResponseMessage Response);
    public abstract object PrepareMessage(SMS Sms);
    protected abstract void SaveResponse(object Sms, HttpResponseMessage Response);
}

The object Sms received in the SendSMS method is the return of the PrepareMessage method. The problem is that PrepareMessage could return an object of different types.

(For example, one of my providers accept requests to send one message or multiple messages, but the object is different for each one. Then I return the correct object in the PrepareMessage method, and the SendSMS method send it as a JSON object to the provider.)

It works but causes some conversion problems in the child classes during the development, and I was wondering if there is a better way to do this. I have read some questions about it:

C# Return Different Types?

Generic method that can return different types

I think that overload methods using Generics would be better, but I have no experience with C#.

Any ideas?

  • You can use `dynamic` as return type. – Anamnian Jul 24 '18 at 13:36
  • Hm, it seems to work fine! Do you know if there is some difference in performance using dynamic x using generics? I expect that it will be called by many users at the same time. – Caio Amaral Jul 24 '18 at 13:47
  • 1
    You can read it yourself https://stackoverflow.com/a/828401/6677992 – Anamnian Jul 24 '18 at 13:50
  • Thank you. Gishu's answer says: "Performance: Generics improves the performance for algorithms/code using Value types by a significant order of magnitude. It prevents the whole boxing-unboxing cycle that cost us pre-Generics. Dynamic doesn't do anything for this too." – Caio Amaral Jul 24 '18 at 16:15

2 Answers2

1

I think you are correct, it does sound like a good place to use generics, perhaps something like this, where "T" (called t by convention) is the type you are using.

public abstract class Provider<T>
{
    protected HttpClient _Client;
    protected SMSManagerAPIContext _Context;

    public abstract DeliveryResponse SendSMS(T Sms);
    protected abstract DeliveryResponse ParseResponse(HttpResponseMessage Response);
    public abstract T PrepareMessage(SMS Sms);
    protected abstract void SaveResponse(object Sms, HttpResponseMessage Response);
}


public class SMS {
    public string Message { get; set; }
}

public class SMSManagerAPIContext{}


public class DeliveryResponse { }


public class PremiumSMS
{
    public double Cost { get; set; }
    public string Message { get; set; }

}


public class PremiumSmsProvider : Provider<PremiumSMS>
{
    public const double Cost = 3.99;

    public override PremiumSMS PrepareMessage(SMS Sms)
    {
        return new PremiumSMS {
            Message = Sms.Message, 
            Cost = Cost
        };
    }

    public override DeliveryResponse SendSMS(PremiumSMS Sms)
    {
        throw new NotImplementedException();
    }

    protected override DeliveryResponse ParseResponse(HttpResponseMessage Response)
    {
        throw new NotImplementedException();
    }

    protected override void SaveResponse(object Sms, HttpResponseMessage Response)
    {
        throw new NotImplementedException();
    }
}

public class FreeSMS
{

    public string Message { get; set; }

}



public class FreeSmsProvider : Provider<FreeSMS>
{
    public override FreeSMS PrepareMessage(SMS Sms)
    {
        return new FreeSMS{
            Message = Sms.Message
        };
    }

    public override DeliveryResponse SendSMS(FreeSMS Sms)
    {
        throw new NotImplementedException();
    }

    protected override DeliveryResponse ParseResponse(HttpResponseMessage Response)
    {
        throw new NotImplementedException();
    }

    protected override void SaveResponse(object Sms, HttpResponseMessage Response)
    {
        throw new NotImplementedException();
    }
}

Here's more info on generic classes: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/generic-classes

Alex KeySmith
  • 16,657
  • 11
  • 74
  • 152
  • Thank you Alex. But there is a way to return a second object type in the method **PrepareMessage**? I would like to use the same method but making possible to choose the return type inside the method. Is it possible? – Caio Amaral Jul 24 '18 at 13:56
  • @CaioAmaral I've added an additional example if using this technique. Alternatively if you wished to share parts of the code you could have generics and inheritance (or composition). – Alex KeySmith Jul 24 '18 at 14:01
  • I'd suggest using a mixture of my example and refactoring your code to use interfaces and a factory pattern as suggested in the other answer. – Alex KeySmith Jul 24 '18 at 14:02
1

Use a factory. The code using this probably only cares only to give a string message to some phone number, and get back whether that is successful or not and possibly the error message if unsuccessful.

The creation of the message itself to adhere to different providers/implementations should just be in a black box. The different providers will just have different constructors.

public interface ISmsProvider 
{
    (bool, string) SendMessage(string number, string message);
}

public class SampleSmsProvider1 : ISmsProvider
{
    public SampleSmsProvider1(string userKey, string passKey) 
    {
        // initialize           
    }

    public (bool, string) SendMessage(string number, string message) 
    {
        // send the message (using a provider implementation from NuGet perhaps)

        // return success/fail and error message, if applicable
        return (true, string.Empty);
    }
}

public class SmsFactory
{
    public ISmsProvider GetProvider() 
    {
        // Initialize and return one of the ISmsProvider implementations, depending on (I guess) the configuration
    }
}
pkr
  • 1,723
  • 5
  • 25
  • 43
  • Although this answer doesn't directly answer your question regarding handling different return types. I think pkr298's answer hits the crux of the problem that rather than solving the return type problem, consumers of your class will likely only be interested in _sending a message_ not how it is sent and you should likely want to refactor to use interfaces and the factory or provider pattern. +1 from me. – Alex KeySmith Jul 24 '18 at 13:57