Solved

Problem with Generic Interface Inheritance

Posted on 2011-02-16
5
615 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
  • 3
  • 2
5 Comments
 
LVL 33

Expert Comment

by:Todd Gerbert
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
Thanks - that should get things moving!
0
 
LVL 33

Expert Comment

by:Todd Gerbert
Comment Utility
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

Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

Join & Write a Comment

Recently while returning home from work my wife (another .NET developer) was murmuring something. On further poking she said that she has been assigned a task where she has to serialize and deserialize objects and she is afraid of serialization. Wha…
This article is for Object-Oriented Programming (OOP) beginners. An Interface contains declarations of events, indexers, methods and/or properties. Any class which implements the Interface should provide the concrete implementation for each Inter…
Here's a very brief overview of the methods PRTG Network Monitor (https://www.paessler.com/prtg) offers for monitoring bandwidth, to help you decide which methods you´d like to investigate in more detail.  The methods are covered in more detail in o…
This tutorial demonstrates a quick way of adding group price to multiple Magento products.

743 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

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now