Stuart Brant
Stuart Brant

Reputation: 155

cast as generic interface returns null

I have an interface:

public interface IReminder<T> where T : class, IIdentifiableEntity
{
    IEnumerable<T> GetRemindersToBeSent(IRepository<T> repository);

}

and class TimesheetReminder that implements this interface:

public class TimesheetReminder : IReminder<InvoiceSummary>
{

    public IEnumerable<InvoiceSummary> GetRemindersToBeSent(IRepository<InvoiceSummary> invoiceSummaryRepository)
    {
        var today = DateTime.Today;
        return invoiceSummaryRepository.List.Where(inv =>
            inv.InvoiceSummaryStatus.CKAStatusName == "Draft" &&
            inv.InsertDateUTC <= today.AddDays(-3)            &&
            inv.InsertDateUTC >= today.AddDays(-6)            &&
            inv.EndDate       <= today.AddDays(-3)
        );
    }

The InvoiceSummary implements IIdentifyableEntity, yet

public static class ReminderFactory<T> where T : class, IIdentifiableEntity
{
    public static IReminder<T> GetReminder(string applicationType)
    {
        IReminder<T> reminder;

        switch (applicationType)
        {
            case "Invoicing":
                reminder = (IReminder<T>)new TimesheetReminder();
                break;
            default:
                reminder = null;
                break;
        }

        return reminder;
    }
}

Invoicing case returns null.

If TimesheetReminder didn't implement IReminder of an IIdentifiableEntity I would understand it, but it does.

What am I doing wrong?

Upvotes: 3

Views: 917

Answers (4)

hpsanampudi
hpsanampudi

Reputation: 679

For any other reason, if you still want to pass interface (IIdentifiableEntity) while creating factory instance

var factory = ReminderFactory<IIdentifiableEntity>.GetReminder("Invoicing");

Solution (Proof of concept): Which I don't prefer since it is bad design.

  1. We need to change interfaces (IReminder, IRepository) as covaraint.

  2. While reading IIdentifiableEntity data we need to cast to corresponding concreate class type.

Sample Code shown below: Please follow code comments

public class Program
{
    static void Main(string[] args)
    {
        //Hp --> Note: While creating factory instance we are passing interface type.
        var factory = ReminderFactory<IIdentifiableEntity>.GetReminder("Invoicing");
        var x = factory.GetRemindersToBeSent(new InvoiceRepository());

        //Hp --> Note: While reading data we need to cast to corrsponding concreate class type.
        x.Cast<InvoiceSummary>().ToList().ForEach(item =>
        Console.WriteLine($"{item.EntityName}:{item.TotalAmount}"));
        Console.ReadKey();
    }
}

public interface IRepository<out T>
{
    // Hp --> Note: You can't use setter since T it is out parameter (covariant)
    IEnumerable<T> List { get; }
}

public interface IReminder<out T> where T : class, IIdentifiableEntity
{
    //Hp --> Note: You can't use IRepository<T> here since T is out parameter (covariant)
    //Instead of T use interface IIdentifiableEntity
    IEnumerable<T> GetRemindersToBeSent(IRepository<IIdentifiableEntity> repository);
}

public class TimesheetReminder : IReminder<InvoiceSummary>
{
    public IEnumerable<InvoiceSummary> GetRemindersToBeSent(
        //Hp --> Note: We need to cast IIdentifiableEntity to corrsponding concreate class type.
        IRepository<IIdentifiableEntity> repository) =>
        repository.List.Where(I => IsEqual("Invoice", I.EntityName)).Cast<InvoiceSummary>();

    private bool IsEqual(string source, string target) =>
       string.Equals(source, target, StringComparison.CurrentCultureIgnoreCase);
}

public interface IIdentifiableEntity
{
    string EntityName { get; set; }
}

public static class ReminderFactory<T> where T : class, IIdentifiableEntity
{
    public static IReminder<T> GetReminder(string applicationType)
    {
        IReminder<T> reminder;
        switch (applicationType)
        {
            case "Invoicing":
                reminder = new TimesheetReminder() as IReminder<T>;
                break;
            default:
                reminder = null;
                break;
        }
        return reminder;
    }
}

public class InvoiceRepository : IRepository<InvoiceSummary>
{
    public IEnumerable<InvoiceSummary> List => new List<InvoiceSummary> {
        new InvoiceSummary { EntityName = "Invoice", TotalAmount = 100.00M } };
}

public class InvoiceSummary : IIdentifiableEntity
{
    public string EntityName { get; set; }
    public decimal TotalAmount { get; set; }
}

Upvotes: 0

hpsanampudi
hpsanampudi

Reputation: 679

I think you are creating factory instance by passing IIdentifiableEntity interface as shown below:

var factory = ReminderFactory<IIdentifiableEntity>.GetReminder("Invoicing");

Which will always return null even when we type cast

reminder = new TimesheetReminder() as IReminder<T>;

Solution:

While creating factory instance, we need to pass concrete class type of InvoiceSummary

var factory = ReminderFactory<InvoiceSummary>.GetReminder("Invoicing");

Even we can modify creating factory instance by removing hard coded string "Invoicing"

var factory = ReminderFactory.Create<InvoiceSummary>();

The sample code is shown below:

public class Program
{
    static void Main(string[] args)
    {
        //Hp --> Logic: Create factory instance by passing concrete class type.
        var factory = ReminderFactory.Create<InvoiceSummary>();
        var x = factory.GetRemindersToBeSent(new InvoiceRepository());
        x.ToList().ForEach(item =>
        Console.WriteLine($"{item.EntityName}:{item.TotalAmount}"));

        Console.ReadKey();
    }
}

public interface IIdentifiableEntity
{
    string EntityName { get; set; }
}

public static class ReminderFactory
{
    public static IReminder<T> Create<T>() where T : IIdentifiableEntity
    {
        IReminder<T> reminder = null;
        if (typeof(InvoiceSummary) == typeof(T))
        {
            reminder = new TimesheetReminder() as IReminder<T>;
        }

        return reminder;
    }
}

public interface IReminder<T> where T : IIdentifiableEntity
{
    IEnumerable<T> GetRemindersToBeSent(IRepository<T> repository);
}

public interface IRepository<T>
{
    IEnumerable<T> List { get; }
}

public class InvoiceRepository : IRepository<InvoiceSummary>
{
    public IEnumerable<InvoiceSummary> List => new List<InvoiceSummary> {
        new InvoiceSummary { EntityName = "Invoice", TotalAmount = 100.00M } };
}

public class InvoiceSummary : IIdentifiableEntity
{
    public string EntityName { get; set; }
    public decimal TotalAmount { get; set; }
}

public class TimesheetReminder : IReminder<InvoiceSummary>
{
    public IEnumerable<InvoiceSummary> GetRemindersToBeSent(
        IRepository<InvoiceSummary> repository) =>
        repository.List.Where(I => IsEqual("Invoice", I.EntityName));

    private bool IsEqual(string source, string target) =>
       string.Equals(source, target, StringComparison.CurrentCultureIgnoreCase);
}

Upvotes: 0

Rudra
Rudra

Reputation: 144

Try following....

IReminder<InvoiceSummary> reminder = new TimesheetReminder() as IReminder<InvoiceSummary>; 

Upvotes: 1

InBetween
InBetween

Reputation: 32790

What is T? TimeSheetReminder is IReminder<InvoiceSummary> so if T is not InvoiceSummary then the reference conversion is not possible:

class Foo: IIdentifiableEntity { ... }
var reminder = new TimesheetReminder() as IReminder<Foo>; //returns null

Upvotes: 4

Related Questions