Collection of Generic Types

Collection of generic types

Have your generic class inherit from a non-generic base, or implement a non-generic interface. Then you can have a collection of this type and cast within whatever code you use to access the collection's contents.

Here's an example.

public abstract class MyClass
{
public abstract Type Type { get; }
}

public class MyClass<T> : MyClass
{
public override Type Type
{
get { return typeof(T); }
}

public T Value { get; set; }
}

// VERY basic illustration of how you might construct a collection
// of MyClass<T> objects.
public class MyClassCollection
{
private Dictionary<Type, MyClass> _dictionary;

public MyClassCollection()
{
_dictionary = new Dictionary<Type, MyClass>();
}

public void Put<T>(MyClass<T> item)
{
_dictionary[typeof(T)] = item;
}

public MyClass<T> Get<T>()
{
return _dictionary[typeof(T)] as MyClass<T>;
}
}

Java - Generic types and collections

Java does not allow the creation of generic Arrays. The Java Collection Classes are mainly implemented using Object arrays. The ArrayList class may look like the following

public class ArrayList<T> implements List<T>, Serializable {
private transient Object[] data;
// more content...
}

When creating a new Instance of the ArrayList a new Object[] array is created that can hold objects of any type. Typesafety is only achieved through using the generic Type Parameter.

Since List did not provide any Type parameter it makes use of the rawtype and anything can be added to the list. Therefore always make sure to infer template arguments to keep the typesafety.

public void func(int k, List<Integer> list) {
list.add(9); // works
list.add(true); // compile error
list.add("a string"); // compile error
}

You should never use rawtypes. Depending on your compiler settings warnings will be omitted.
It's better to use (bound/unbound) wildcards.

Different Generics T in the same collection

In order to be stored in a List<T> together the columns must have a common base type. The closest common base class of DateColumn and NumberColumn is object. Neither derives from Column<T> but instead a specific and different instantiation of Column<T>.

One solution here is to introduce a non-generic Column type which Column<T> derives from and store that in the List

public abstract class Column { 
public abstract object ValueUntyped { get; }
}

public abstract class Column<T> : Column {
public T Value { get; set; }
public override object ValueUntyped { get { return Value; } }
}

...

IList<Column> list = new List<Column>();
list.Add(new DateColumn());
list.Add(new NumberColumn());

Generic Types Collection

Basically the answer is: correct, you can't do that. Swift needs to determine the concrete types of type parameters at compile time, not at runtime. This comes up in a lot of little corner cases. For instance, you can't construct a generic closure and store it in a variable without type-specifying it.

This can be a little clearer if we boil it down to a minimal test case

protocol Creatable { init() }

struct Object : Creatable { init() {} }

func instantiate<T: Creatable>(Thing: T.Type) -> T {
return Thing()
}

// works. object is of type "Object"
let object = instantiate(Object.self) // (1)

// 'Creatable.Type' is not convertible to 'T.Type'
let type: Creatable.Type = Object.self
let thing = instantiate(type) // (2)

At line 1, the compiler has a question: what type should T be in this instance of instantiate? And that's easy, it should be Object. That's a concrete type, so everything is fine.

At line 2, there's no concrete type that Swift can make T. All it has is Creatable, which is an abstract type (we know by code inspection the actual value of type, but Swift doesn't consider the value, just the type). It's ok to take and return protocols, but it's not ok to make them into type parameters. It's just not legal Swift today.

This is hinted at in the Swift Programming Language: Generic Parameters and Arguments:

When you declare a generic type, function, or initializer, you specify the type parameters that the generic type, function, or initializer can work with. These type parameters act as placeholders that are replaced by actual concrete type arguments when an instance of a generic type is created or a generic function or initializer is called. (emphasis mine)

You'll need to do whatever you're trying to do another way in Swift.

As a fun bonus, try explicitly asking for the impossible:

let thing = instantiate(Creatable.self)

And... swift crashes.


From your further comments, I think closures do exactly what you're looking for. You've made your protocol require trivial construction (init()), but that's an unnecessary restriction. You just need the caller to tell the function how to construct the object. That's easy with a closure, and there is no need for type parameterization at all this way. This isn't a work-around; I believe this is the better way to implement that pattern you're describing. Consider the following (some minor changes to make the example more Swift-like):

// Removed init(). There's no need for it to be trivially creatable.
// Cocoa protocols that indicate a method generally end in "ing"
// (NSCopying, NSCoding, NSLocking). They do not include "I"
public protocol Speaking {
func speak()
}

// Converted these to structs since that's all that's required for
// this example, but it works as well for classes.
struct Cat : Speaking {
func speak() {
println("Meow");
}
}

struct Dog : Speaking {
func speak() {
println("Woof");
}
}

// Demonstrating a more complex object that is easy with closures,
// but hard with your original protocol
struct Person: Speaking {
let name: String
func speak() {
println("My name is \(name)")
}
}

// Removed Test class. There was no need for it in the example,
// but it works fine if you add it.
// You pass a closure that returns a Speaking. We don't care *how* it does
// that. It doesn't have to be by construction. It could return an existing one.
func instantiateAndCallSpeak(builder: () -> Speaking) {
let animal = builder()
animal.speak()
}

// Can call with an immediate form.
// Note that Cat and Dog are not created here. They are not created until builder()
// is called above. @autoclosure would avoid the braces, but I typically avoid it.
instantiateAndCallSpeak { Cat() }
instantiateAndCallSpeak { Dog() }

// Can put them in an array, though we do have to specify the type here. You could
// create a "typealias SpeakingBuilder = () -> Speaking" if that came up a lot.
// Again note that no Speaking objects are created here. These are closures that
// will generate objects when applied.
// Notice how easy it is to pass parameters here? These don't all have to have the
// same initializers.
let animalBuilders: [() -> Speaking] = [{ Cat() } , { Dog() }, { Person(name: "Rob") }]

for animal in animalBuilders {
instantiateAndCallSpeak(animal)
}

Kotlin: how to pass collection into generic type of a function

You cannot override a generic function with a fixed parameter, instead, you may have the type parameter on a class or interface to make it. E.g.

abstract class BaseClass<T> {
abstract fun updateValue(value: T) : Unit
}

///class to accept _only_ List<Int>
class ListClass<List<Int>> {
override fun updateValue(value: List<Int>) { /*...*/ }
}

/// class to accept _only_ String
class StringClass<String> {
override fun updateValue(value: String) { /*...*/ }
}

The rationale is as follows when you declare a generic function, e.g. fun <T> updateValue(value: T) is mean it is supposed to work for every possible T. It will not do a pattern matching to find the best suitable match.

You may try checking types in the implementation of the generic function to support a specific case, but in general, it could be hard for collection classes because of type erasure.

fun <T> updateValue(value: T) {
when (value) {
is String -> handleStringValue(value)
is List<*> -> handleListValue(value)
else -> handleDefault(value)
}
}

You cannot tell List<Int> from List<String> here. An inline fun with reified generics may help.

Collection of generic types

Create a non-generic interface or base class, which probably includes everything FormField does except the type-specific bits. Then you can have an ICollection<IFormField>. Obviously you won't be able to use this in a strongly-typed way, in terms of the type of field being used - but you can use all the non-type-specific bits of it (e.g. the name and the form).

Generic types, collections and object reference

You can also look at covariance in generics.

You also need to define a common interface for GenericClass but it can be generic:

interface IType { }
interface IGenericClass<out T> where T : IType { }
class Type1 : IType { }
class Type2 : IType { }
class GenericClass<T> : IGenericClass<T> where T : IType { }

class Program
{
static void Main(string[] args)
{
Dictionary<int, IGenericClass<IType>> dict = new Dictionary<int, IGenericClass<IType>>();
dict[0] = new GenericClass<Type2>();
dict[1] = new GenericClass<Type1>();
}
}

But it won't allow:

 Dictionary<int, IGenericClass<object>> dict = new Dictionary<int, IGenericClass<object>>();

Edit: For completeness

You can't use this to pass IType as parameter in IGenericClass. It requires contravariance and using contravariance will break the assignment to Dictionary<int, IGenericClass<IType>> dict:

A covariant type parameter is marked with the out keyword (Out keyword
in Visual Basic, + for the MSIL Assembler). You can use a covariant
type parameter as the return value of a method that belongs to an
interface, or as the return type of a delegate. You cannot use a
covariant type parameter as a generic type constraint for interface
methods.

 interface IGenericClass<out T> where T : IType
{
T GetType(); //possible
void SetType(T t); //not possible
}


Related Topics



Leave a reply



Submit