简体   繁体   中英

C#: How do I create an instance with a generic while it has been declared as object

I am facing a problem where I want to create an instance of a SomeClass<T> where T is giving by the method, but SomeClass<T> is added to a dictionary that is declared like ConcurrentDictionary<OtherClass, SomeClass<object>> . What I want to do does not compile because Type 'T' doesn't match expected type object

private readonly SemaphoreSlim semaphoreSlim;
private readonly ConcurrentDictionary<StatisticType, StatisticsList<object>> statisticsDictionary;

public StatisticsDictionary()
{
    semaphoreSlim = new SemaphoreSlim(1);
    statisticsDictionary = new ConcurrentDictionary<StatisticType, StatisticsList<object>>();
}

(...)

public bool TryAdd<T>(StatisticType key, DateTime dateTime, T value)
{
    if (!statisticsDictionary.ContainsKey(key))
    {
        semaphoreSlim.Wait();
        statisticsDictionary.TryAdd(key, new StatisticsList<T>());
        semaphoreSlim.Release();
    }

    if (!statisticsDictionary.TryGetValue(key, out var statistics))
        return false;

    statistics.Add(dateTime, value);
    return true;
}

The concurrentDictionary is wrapped in a class that needs to support different types of 'T' (like long, DateTime, TimeSpan and possibly other types)

I've tried to just initialize SomeClass<T> as SomeClass<object> so that it matches the declaration. And then on retrieval cast object to T that is given by the method. But this leads to casting exceptions because it seems to be impossible to (explicitly) cast an object to, for example, long even when the object is in fact a long during run time.

public bool TryGetFrom<T>(StatisticType key, DateTime from, out IEnumerable<(DateTime, T)> value)
{
    if (statisticsDictionary.TryGetValue(key, out var statistics))
    {
        var list = statistics.TryGetFrom(from);
        value = (IEnumerable<(DateTime, T)>) list;

        return value != null;
    }

    value = null;
    return false;
}

Is it even possible what I try to accomplish?


Edit Source code of StatisticsList as requested in the comments

public class StatisticsList<T>
{
    private readonly SemaphoreSlim semaphoreSlim;
    private readonly SortedList<DateTime, (DateTime, T)> sortedList;
    public DateTime StartedAt { get; }
    public int Count => sortedList.Count;

    public StatisticsList()
    {
        try
        {
            semaphoreSlim.Wait();
            sortedList.Add(key, (key, value));
        }
        finally
        {
            semaphoreSlim.Release();
        }
    }

    public void Add(DateTime key, T value)
    {
        semaphoreSlim.Wait();
        sortedList.Add(key, (key, value));
        semaphoreSlim.Release();
    }

    public bool TryGetFrom(DateTime from, out IEnumerable<(DateTime, T)> value)
    {
        try
        {
            semaphoreSlim.Wait();
            value = sortedList.Where(s => s.Key > from).Select(s => s.Value).ToList();
            return true;
        }
        catch (ArgumentNullException) { }
        finally
        {
            semaphoreSlim.Release();
        }

        value = null;
        return false;
    }

    public bool TryGetLast(out (DateTime, T) value)
    {
        try
        {
            semaphoreSlim.Wait();
            var dateTime = sortedList.Max(s => s.Key);
            return sortedList.TryGetValue(dateTime, out value);
        }
        catch (ArgumentNullException)
        {
            value = (default, default);
        }
        finally
        {
            semaphoreSlim.Release();
        }

        return false;
    }

    public IEnumerable<(DateTime, T)> ToList()
    {
        try
        {
            semaphoreSlim.Wait();
            return sortedList.Values.ToList();
        }
        finally
        {
            semaphoreSlim.Release();
        }
    }
}

Generics are effective when you know the type during design-time. In your case, your collection can store items of any different types.

You can utilize a pretty popular trick of declaring non-generic and inherited generic version of a collection item. As a side effect, you can get rid of a not-very-readable value tuple:)

Declare item class like this:

public abstract class StatisticsListItem
{
    public DateTime Time { get; protected set; }
}

public class StatisticsListItem<T> : StatisticsListItem 
{
    public StatisticsListItem(DateTime time, T value)
    {
        Time = time;
        Value = value;
    }

    public T Value { get; }
}

And then you can store them as StatisticsListItem and perform operations depending on their specific sub-type. It is also type-safe, because you cannot access the value of a generic list item, like in case if you declare it as non-generic StatisticsListItem with object Value { get; } object Value { get; } property.

The simplest usage scenario can look like this:

public class StatisticsList
{
    public List<StatisticsListItem> Values { get; set; } = new List<StatisticsListItem>();

    public void TryAdd<T>(DateTime dateTime, T value)
    {
        // Adding an item of generic type to non-generic list:
        Values.Add(new StatisticsListItem<T>(dateTime, value));
    }

    public IEnumerable<StatisticsListItem<T>> TryGetFrom<T>(DateTime dateTime) 
    {  
        // Dynamically filtering only items of specific generic type
        return Values
            .OfType<StatisticsListItem<T>>() // computationally ineffective, subject to improvement ;)
            .Where(t => t.Time >= dateTime); // same, just an example
    }
}

It is just an example. You still need to implement it in a way it supports keys, fits into your dictionary and makes filtering of items by type much more effective. Good luck!

Some minor comments: you still might have some problems with handling multi-threading, but this is out of the scope of this question:)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM