Solved

Problem with Generic Interface Inheritance

Posted on 2011-02-16
5
621 Views
Last Modified: 2012-06-27
I have the following interfaces and concrete classes which attempt to define a system for sending messages (email, fax, sms etc.):

 
public interface IMessageSender<T> where T : IMessage
    {
        bool ExecuteSend(T messageToSend);
    }

public interface IMailSender<T> : IMessageSender<T> where T : IMailMessage
    {
    }

 public class MailSender<T> : IMailSender<T> where T : IMailMessage
    {
        public bool ExecuteSend(T messageToSend)
        {
            throw new NotImplementedException();
        }
    }

Open in new window


Effectively, this defines a general IMessageSender interface which IMailSender (or ISmsSender, IFaxSender etc.) can use.  The interfaces send "message objects" which ultimately inherit from IMessage - in this case IMailMessage inherits from IMessage.

I want the IMailSender interface to narrow the type of IMessage that is acceptable to the interface - this is the only real purpose of this "intermediate" interface.  The concrete MailSender then just implements the interface.  For info, the ExecuteSend just contains the specific logic for send the particular type of message, so in this example it would execute some variety of Smtp send logic.

The IMessage, IMailMessage interfaces and concrete MailMessage class are defined as:

public interface IMessage
    {
        void Send<T>(T messageSender) where T : IMessageSender<IMessage>;

    }

public interface IMailMessage : IMessage {
  void Send(IMailSender<IMailMessage> messageSender);
}

    public class MailMessageWrapper : IMailMessage {

}

public void Send(IMailSender<IMailMessage> messageSender) {
     // automatic pre-send logic here.
     messageSender.ExecuteSend(this);
}

Open in new window


The problem comes when using IMessage.  I want to be able to make the message responsible for sending itself - i.e. inject a IMessageSender into the message rather than inject an IMessage into an IMessageSender. this is because it may be necessary to execute some automatic operation on the message before sending.

My concrete class is throwing an error that the interface member IMessage.Send<T>(T) is not implemented.

I need to find a way around this that will still allow me to restrict the IMessageSender to the appropriate type - i.e. in the concrete class I want to write (or some slight variation on):

public void Send(IMailSender<IMailMessage> messageSender)

NOT

void Send<T>(T messageSender) where T : IMessageSender<IMessage>
0
Comment
Question by:pipelineconsulting
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 3
  • 2
5 Comments
 
LVL 33

Expert Comment

by:Todd Gerbert
ID: 34907118
It's early in the morning, I haven't had my coffee yet, and your question is a little tricky for me to wrap my half-conscious brain around, so forgive me if I'm way off base...

Are generics really necessary here?  That is, give:
public interface IMessageSender<T> where T : IMessage
{
        bool ExecuteSend(T messageToSend);
}

Open in new window



Doesn't this code block achieve precisely the same thing, considering that your limiting T to IMessage so you know whatever type is passed in to T will always be able to be cast to an IMessage, so:
public interface IMessageSender
{
    bool ExecuteSend(IMessage messageToSend);
}

Open in new window


0
 

Author Comment

by:pipelineconsulting
ID: 34907213
The reason I did this (and my head is a bit fuzzy now) is to have the ability to restrict the sender to accept a certain type of message further down the inheritance line.

So, for an ISmsSender, I only want it to deal with ISmsMessages rather than say an IMailMessage (which obviously both inherit from IMessage)

For the concrete classes, this would end up something like

 
public class MailSender<IMailMessage> {
  bool ExecuteSend(IMailMessage messageToSend) {}
}

public class SmsSender<ISmsMessage> {
  bool ExecuteSend(ISmsMessage messageToSend) {}
}

Open in new window


but not

 
public class MailSender<ISmsMessage> {
  bool ExecuteSend(ISmsMessage messageToSend) {}
}

Open in new window

0
 
LVL 33

Accepted Solution

by:
Todd Gerbert earned 500 total points
ID: 35021289
I'm not still not sure I'm following your intended logic - or if this suggestion even makes sense, but...

The point of this code is that common pieces of all messages, e.g. the "To" property, are accessible to any method by casting the message to MyMessageBase, and does not require special knowledge of a more specific SmsMessage or MailMessage.  The interface in this example I think is superfluous, but at any rate, the inheritance hierarchy also means that a method can send a message without knowing anything about the message, other than it implements ISendableMessage or inherits from MyMessageBase.

The sender in my example is a seperate class that's assigned to a property of the message (meeting your requirement of the message being able to send itself), but as I think about it more I'm not sure that makes sense - since a MailMessage will only ever be sent with a MailSender, and a FaxMessage would only ever be sent with a FaxSender, wouldn't it be easier just to put the sending code in the MailMessage and FaxMessage classes?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

class Program
{
	static void Main(string[] args)
	{
		MySmsMessage smsMessage = new MySmsMessage();
		smsMessage.From = "555-555-1212@domain.com";
		smsMessage.To = "5551231212@carrier.com";
		smsMessage.MessageBody = "Hello world";
		smsMessage.MessageSender = new SmsSender(smsMessage);

		MyMailMessage mailMessage = new MyMailMessage();
		mailMessage.From = "user@domain.com";
		mailMessage.IsBodyHtml = false;
		mailMessage.MessageBody = "Hello World!";
		mailMessage.Subject = "Test Message";
		mailMessage.To = "otheruser@otherdomain.com";
		mailMessage.MessageSender = new EmailSender(mailMessage);

		sendMethodOne(smsMessage);
		sendMethodOne(mailMessage);

		sendMethodTwo(smsMessage);
		sendMethodTwo(mailMessage);
	}

	static void sendMethodOne(ISendableMessage message)
	{
		message.Send();
	}

	static void sendMethodTwo(MyMessageBase message)
	{
		message.MessageBody += "...sent with method two.";
		message.Send();
	}
}

Open in new window

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Mail;

public interface ISendableMessage
{
	void Send();
}

public class MyMessageBase : ISendableMessage
{
	public event EventHandler MessageSent;
	public virtual string To { get; set; }
	public virtual string From { get; set; }
	public virtual string MessageBody { get; set; }

	public virtual void Send()
	{
		OnMessageSent(new EventArgs());
	}

	protected virtual void OnMessageSent(EventArgs e)
	{
		if (MessageSent != null)
			MessageSent(this, e);
	}
}

public class EmailSender
{
	private MyMailMessage _owningMessage;
	public EmailSender(MyMailMessage OwningMessage)
	{
		_owningMessage = OwningMessage;
	}

	public void Send()
	{
		using (SmtpClient smtp = new SmtpClient("192.168.1.14"))
		{
			using (MailMessage message = new MailMessage())
			{
				message.Body = _owningMessage.MessageBody;
				message.From = new MailAddress(_owningMessage.From);
				message.IsBodyHtml = _owningMessage.IsBodyHtml;
				message.Subject = "MyMailMessage: " + _owningMessage.Subject;
				message.To.Add(_owningMessage.To);

				smtp.Send(message);
			}
		}
	}
}

public class SmsSender
{
	private MySmsMessage _owningMessage;
	public SmsSender(MySmsMessage OwningMessage)
	{
		_owningMessage = OwningMessage;
	}

	public void Send()
	{
		using (SmtpClient smslib = new SmtpClient("192.168.1.14"))
		{
			using (MailMessage message = new MailMessage())
			{
				message.Body = _owningMessage.MessageBody;
				message.From = new MailAddress(_owningMessage.From);
				message.To.Add(_owningMessage.To);
				message.Subject = "MySmsMessage";

				smslib.Send(message);
			}
		}
	}
}

public class MyMailMessage : MyMessageBase
{
	public EmailSender MessageSender { get; set; }
	public bool IsBodyHtml { get; set; }
	public string Subject { get; set; }

	public override void Send()
	{
		if (this.MessageSender != null)
		{
			this.MessageSender.Send();
			base.Send();
		}
	}
}

public class MySmsMessage : MyMessageBase
{
	public SmsSender MessageSender { get; set; }

	private string _messageBody;
	public override string MessageBody
	{
		get { return _messageBody; }
		set
		{
			if (value.Length > 160)
				throw new Exception("SMS Messages may not exceed 160 characters in length.");
			else
				_messageBody = value;
		}
	}

	public override void Send()
	{
		if (this.MessageSender != null)
		{
			this.MessageSender.Send();
			base.Send();
		}
	}
}

Open in new window

0
 

Author Closing Comment

by:pipelineconsulting
ID: 35026856
Thanks - that should get things moving!
0
 
LVL 33

Expert Comment

by:Todd Gerbert
ID: 35030237
Hope that actually helped - that was a bit of a brain-teaser question, took me a couple days to wrap my head around it and I'm not quite sure I succeeded. ;)
0

Featured Post

On Demand Webinar - Networking for the Cloud Era

This webinar discusses:
-Common barriers companies experience when moving to the cloud
-How SD-WAN changes the way we look at networks
-Best practices customers should employ moving forward with cloud migration
-What happens behind the scenes of SteelConnect’s one-click button

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

More often than not, we developers are confronted with a need: a need to make some kind of magic happen via code. Whether it is for a client, for the boss, or for our own personal projects, the need must be satisfied. Most of the time, the Framework…
A long time ago (May 2011), I have written an article showing you how to create a DLL using Visual Studio 2005 to be hosted in SQL Server 2005. That was valid at that time and it is still valid if you are still using these versions. You can still re…
A short tutorial showing how to set up an email signature in Outlook on the Web (previously known as OWA). For free email signatures designs, visit https://www.mail-signatures.com/articles/signature-templates/?sts=6651 If you want to manage em…
Finds all prime numbers in a range requested and places them in a public primes() array. I've demostrated a template size of 30 (2 * 3 * 5) but larger templates can be built such 210  (2 * 3 * 5 * 7) or 2310  (2 * 3 * 5 * 7 * 11). The larger templa…

726 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question