简体   繁体   中英

What is the “type” of a generic IList<T>?

Imagine an extension like this..

public static Blah<T>(this IList<T> ra)
 {
 ..
 }

Imagine you want to make a note of the most recently-called one.

private static IList recent;
public static Blah<T>(this IList<T> ra)
 {
 recent = ra;
 ..
 }

You actually can not do that:

error CS0266: Cannot implicitly convert type System.Collections.Generic.IList<T> to System.Collections.IList .

1- You can simply make recent an object and that seems to work fine, but it seems like a poor solution.

2- It seems if you do have recent as an IList , you can actually cast the "ra" to that...

recent = (System.Collections.IList)ra;

and it seems to work. Seems strange though?? So,

3- Actually, what type should recent be so that you don't have to cast to it?? How can you make recent the same type as ra ? You can't say this ....

private static System.Collections.Generic.IList recent;

it's not meaningful. So what the heck is the type of "ra"? What should recent "be" so that you can simply say recent=ra ?

(I mention this is for Unity, since you constantly use generic extensions in Unity.)


4- Consider aa further difficulty the case if you want to have a Dictionary of them all.

private static Dictionary<object,int> recents = new Dictionary<object,int>();

I can really only see how to do it as an object.


USE CASE EXAMPLE.

Here's an extension you use constantly, everywhere, in game engineering,

public static T AnyOne<T>(this IList<T> ra)
    {
    int k = ra.Count;
    if (k<=0) {Debug.Log("Warn!"+k);}
    int r = UnityEngine.Random.Range(0,k);
    return ra[r];
    }

no problem so far. So,

explosions.AnyOne();
yetAnotherEnemyToDefeat = dinosaurStyles.AnyOne();

and so on. However. Of course, actual random selections feel bad; in practice what you want is a fairly non-repeating order, more like a shuffle . Usually the best thing to do with any list or array is shuffle them, and serve them in that order; perhaps shuffle again each time through. Simple example, you have 20 random sound effects roars , being for when the dino roars. Each time you need one, if you do this

 roars.AnyOne();

its OK, but not great. It will sound sort of suck. (Most players will report it as "not being random" or "repeating a lot".) This

 roars.NextOne();

is much better. So, NextOne() should, on its own, (a) if we're at the start shuffle the list, (b) serve it in that order, (c) perhaps shuffle it again each time you use up the list. {There are further subtleties, eg, try not to repeat any near the end/start of the reshuffle, but irrelevant here.}

Note that subclassing List (and/or array) would suck for many obvious reasons, it's a job for a simple self-contained extension.

So then, here's a beautiful way to implement NextOne() using a simple stateful extension.

private static Dictionary<object,int> nextOne = new Dictionary<object,int>();
public static T NextOne<T>(this IList<T> ra)
    {
    if ( ! nextOne.ContainsKey(ra) )
        // i.e., we've never heard about this "ra" before
        nextOne.Add(ra,0);

    int index = nextOne[ra];

    // time to shuffle?
    if (index==0)
        {
        Debug.Log("shuffling!"); // be careful to mutate, don't change the ra!
        IList<T> temp = ra.OrderBy(r => UnityEngine.Random.value).ToList();
        ra.Clear(); foreach(T t in temp) ra.Add(t);
        }

    T result = ra[index];
    ++index;
    index=index%ra.Count;
    nextOne[ra] = index;
    return result;
    }

This is surely the perfect example of a "stateful extension".

Notice indeed, I just used "object".

I guess in a way, the fundamental question in this QA is, is it best to use the Dictionary of "object" there, or, would something else more typey be better? Really that's the question at hand. Cheers!

The simplest, and most type-safe, solution is to store a separate value for each T :

private static class RecentHolder<T> {
    public static IList<T> Value { get; set; }
}
public static Blah<T>(this IList<T> ra) {
    RecentHolder<T>.Value = ra;
}

If you want a single globally most recent IList<T> where T potentially varies each time, then your only options are to use object or dynamic . Both require casting; the latter just casts automatically.

I think your confusion stems from thinking that IList<T> inherits IList - it doesn't :

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable

So arguably you could do this, although I don't see any advantage really:

private static IEnumerable recent;
public static void Blah<T>(this IList<T> ra)
{
    recent = ra;
    ...
}

What is the “type” of a generic IList< T >?

The base type..

Console.WriteLine( new List<int>().GetType().BaseType);

System.Object

The Generic Type definition ...

Console.WriteLine( new List<int>().GetType().GetGenericTypeDefinition());

System.Collections.Generic.List`1[T]

And to expand on SLAKS Answer

Not really. In the absence of a separate common non-generic base class

You can also use interfaces. So you could do...

public interface IName
{
  string Name { get; set; }
}
public class Person : IName
{
  public string Name { get; set; }
}
public class Dog : IName
{
  public string Name { get; set; }
}

Then you could

private static List<IName> recent;
public static Blah<T>(this List<IName> ra)
{
  recent = ra;
  ..
}

and it won't matter if you put Dog or Person in the list.

OR

I can't believe I didn't think about this last night; LINQ to the rescue using object .

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

public class Program
{
    private static class WonkyCache
    {
        private static List<object> cache = new List<object>();

        public static void Add(object myItem)
        {
            cache.Add(myItem);
        }

        public static IEnumerable<T> Get<T>()
        {
            var result = cache.OfType<T>().ToList();
            return result;
        }
    }

    public static void Main()
    {
        WonkyCache.Add(1);
        WonkyCache.Add(2);
        WonkyCache.Add(3);
        WonkyCache.Add(Guid.NewGuid());
        WonkyCache.Add("George");
        WonkyCache.Add("Abraham");

        var numbers = WonkyCache.Get<int>();
        Console.WriteLine(numbers.GetType());           

        foreach(var number in numbers)
        {
            Console.WriteLine(number);
        }

        var strings = WonkyCache.Get<string>();
        Console.WriteLine(strings.GetType());

        foreach(var s in strings)
        {
            Console.WriteLine(s);
        }
    }
}

Results:

System.Collections.Generic.List`1[System.Int32]

1

2

3

System.Collections.Generic.List`1[System.String]

George

Abraham

Try:

public static class StatefulRandomizer<T>
    // Use IEquatable<T> for Intersect()
    where T : IEquatable<T>
{
    // this could be enhanced to be a percentage 
    // of elements instead of hardcoded
    private static Stack<T> _memory = new Stack<T>();
    private static IEnumerable<T> _cache;

    public static void UpdateWith(IEnumerable<T> newCache)
    {
        _cache = newCache.ToList();

        // Setup the stack again, keep only ones that match
        var matching = _memory.Intersect(newCache);
        _memory = new Stack<T>(matching);
    }

    public static T GetNextNonRepeatingRandom()
    {
        var nonrepeaters = _cache
            .Except(_memory);

        // Not familar with unity.. but this should make 
        // sense what I am doing
        var next = nonrepeaters.ElementAt(UnityEngine.Random(0, nonrepeaters.Count()-1));

        // this fast, Stack will know it's count so no GetEnumerator()
        // and _cache List is the same (Count() will call List.Count)
        if (_memory.Count > _cache.Count() / 2)
        {
            _memory.Pop();
        }

        _memory.Push(next);

        return next;
    }
}

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