2

I am trying to cast a generic type to a non generic one that inherits from it but it fails. Check the code below:

    public class Subscription
    {
        public Subscription(DateTime expiration)
        {
            Expiration = expiration;
        }

        public DateTime Expiration { get; set; }
    }

    public class Subscription<T> : Subscription
    {
        public T Parameters { get; }

        public Subscription(DateTime expiration, T data)
            : base(expiration)
        {
            Parameters = data;
        }
    }


    public class SubscriptionsService
    {
        private readonly ConcurrentDictionary<int, object> subscriptions;

        public SubscriptionsService(ConcurrentDictionary<int, object> subscriptions)
        {
            this.subscriptions = subscriptions;
        }

        public void Add<T>(int clientId, DateTime expiration, T parameters)
        {
            if (!subscriptions.TryGetValue(clientId, out var outObject))
            {
                Subscription<T> subscription = new Subscription<T>(expiration, parameters);
                List<Subscription<T>> clientSubscriptions = new List<Subscription<T>>()
                {
                    subscription
                };

                subscriptions.AddOrUpdate(clientId, parameters, (k, v) => parameters);
            }
            else
            {
                if (outObject is List<Subscription<T>> resourceSubscriptions && resourceSubscriptions.Any())
                {
                    Subscription<T> subscription = resourceSubscriptions.SingleOrDefault(x => x.Parameters.Equals(parameters));
                    if (subscription == null)
                    {
                        subscription = new Subscription<T>(expiration, parameters);
                        resourceSubscriptions.Add(subscription);
                    }
                    else
                    {
                        subscription.Expiration = expiration;
                    }
                }
            }
        }

        public void Cleanup()
        {
            // Iterate through each reportId subscription
            foreach (KeyValuePair<int, object> subscriptionKeyValuePair in subscriptions)
            {
                List<Subscription> subscriptions = (subscriptionKeyValuePair.Value as List<Subscription>);
                if (subscriptions == null || !subscriptions.Any())
                    continue;

                foreach (var subscription in subscriptions)
                {
                   if (subscription.Expiration > DateTime.Now)
                   {
                      //some code
                   }
                }
            }
        }
    }

When I call the method Cleanup and iterate through the keyvalue pairs I am trying to cast the values of the dictionary to the non-generic type that I use to add it to the Add Method. I add as List> and try to cast it as List in order to use the only the Expiration property as I dont need the T Parameters inside Cleanup. As a result List<Subscription> subscriptions casts fails and ```susbcriptions`` is always null even if value in the dictionary is not.

codejunkie
  • 83
  • 9
  • Why is `subscriptions` a `ConcurrentDictionary`? Shouldn't it be `ConcurrentDictionary`? Anyway, a `List` can't be converted to a `List`. – Zohar Peled Mar 10 '18 at 06:39
  • Just [one](https://stackoverflow.com/questions/14817306/casting-list-of-derived-class-to-list-of-base-class-still-returning-objects-of-d) of many possible dups – Zohar Peled Mar 10 '18 at 06:43
  • Check this out https://stackoverflow.com/q/48952148/662 9297 – Mohammad Ali Mar 10 '18 at 06:44
  • @ZoharPeled ConcurrentDictionary cannot be like this because for each client there are many subscriptions (List and each one has different parameters – codejunkie Mar 10 '18 at 06:46
  • so it should be `ConcurrentDictionary>` – Zohar Peled Mar 10 '18 at 06:47
  • 3
    casting `Subscribtion` to `Subscription` is one thing and casting `List>` to `List` is another thing. Whole discussion eventually boils down to below cast which is not possible : `List lst = new List()` – rahulaga-msft Mar 10 '18 at 06:47

1 Answers1

0

You are not able to make this cast because the T in List<T> is an invariant type parameter. That means C# does not treat List<A> as a derived type of List<B>, even if A derives from B.

However, IEnumerable<T> has a special feature called a "covariant type parameter". It allows you to do this type of cast. This means that in following example, the lines with list1 and list2 will result in a compiler error, but list3 would work just fine.

public class Program<T>
{
    public void Main()
    {
        IList<Subscription> list1 = new List<Subscription<T>>();
        List<Subscription> list2 = new List<Subscription<T>>();
        IEnumerable<Subscription> list3 = new List<Subscription<T>>();
    }
}

public class Subscription<T> : Subscription
{
}

public class Subscription
{
}

See https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance for a more detailed explanation of covariance.

natemcmaster
  • 25,673
  • 6
  • 78
  • 100
  • `IEnumerable` is not the only type like this. It's also worth explaining why it and other types like `IReadOnlyList` and `Func` can safely be defined this way as it is by no means arbitrary. – Aluan Haddad Mar 10 '18 at 10:07
  • @natemcmaster thanks for your answer. I tried covariance before but I avoided it because I want to remove an item from the original list (thus the code ````if (subscription.Expiration > DateTime.Now)````). Working with an enumerable I am not able to remove it. – codejunkie Mar 13 '18 at 06:41