0

I have written a Collection like this:

class AnimalCollection<TValue> where TValue : Animal, new()
{
    void Add(TValue value){};
    void AddNew()
    {
        Add(new TValue());
    }
}

I have some Classes which are derived from Animal:

class Animal 
{
    string Name;
}
class Fish : Animal
{
    Fish(){};
}
class Mammal : Animal
{
    Mammal(){};
}

As next i want to treat all collections the same way.

static void Main(string[] args)
{
    var FishAquarium = new AnimalCollection<Fish>();
    var MammalEnclosure = new AnimalCollection<Mammal>();

   foo(FishAquarium);
   foo(MammalEnclosure);
}

Question 1: I want to pass every AnimalCollection, which type need 'zoo'?

static void foo(AnimalCollection<Animal> zoo) 
{
    foreach(var animal in Zoo)
        Console.WriteLine(animal.Name);
     zoo.AddNew();
}

Question 2: What is the best practice to generalize a generic class?


Update:

More specific, i have a class which get any AnimalCollection.

class ZooController
{
   public AnimalCollection<Animal> Animals{get; set;}
}
Syrlia
  • 154
  • 2
  • 14
  • 2
    `System.Out.Println(...)`? – FCin Jul 17 '18 at 09:05
  • I would have used an IAnimal interface anyway – Marco Salerno Jul 17 '18 at 09:06
  • it should be Console.WriteLine() instead of System.Out.Println – Ahmad Jul 17 '18 at 09:06
  • Javarp or Cjav? – Marco Salerno Jul 17 '18 at 09:08
  • @MarcoSalerno How can i write a non generic interface with parameter? – Syrlia Jul 17 '18 at 09:08
  • I heard about covariant and read a bit about it. But doesn't understand it. Can you give me an example for my code? – Syrlia Jul 17 '18 at 09:13
  • In fact, since you are using `TValue` as an input parameter to the `Add` method, using a covariant interface type won't work. – mm8 Jul 17 '18 at 09:15
  • An `AnimalCollection` is not an `AnimalCollection` just because a `Fish` is an `Animal` so `foo` should either be generic or accept a common base type. – mm8 Jul 17 '18 at 09:16
  • @mm8 or a covariant interface :) but, my "vote" here is just: use a generic `foo` method – Marc Gravell Jul 17 '18 at 09:24
  • @MarcGravell: See my previous comment about the input parameter. But I do agree with you :) – mm8 Jul 17 '18 at 09:26
  • And why do you want to use a collection instead of a list? – Marco Salerno Jul 17 '18 at 09:43
  • @MarcoSalerno Relating to my origin code: MyCollection is a combination of two other collections from me. The first one uses a HashValue to gurantee unique items. The second uses internal a dictionary to presort my items using a keyitem. Therefore MyCollection allow only TValues which implements two interfaces 'IKeyItem' and 'IHashItem' – Syrlia Jul 17 '18 at 09:48
  • I would have associated just a GUID to every object and putted it in a list by passing a common interface as type – Marco Salerno Jul 17 '18 at 09:52
  • re the edit: "More specific, i have a class which get any AnimalCollection." - no you don't; you have a class that can take `AnimalCollection`, but it **cannot** take `AnimalCollection`, for example - since `AnimalCollection` doesn't offer any kind of variance. – Marc Gravell Jul 17 '18 at 10:23
  • @MarcGravell and that is my Problem. I need a smart solution. – Syrlia Jul 18 '18 at 08:54

2 Answers2

1

Generics would work:

static void foo<T>(AnimalCollection<T> zoo) where T : Animal, new()
{...}

However, it is usually not a good idea to roll your own collection types - it tends to create more confusion than it helps. You may want to consider justList<T>, or IList<T> at a push.


You say you don't want to use generics; that is IMO a silly decision since they solve exactly this problem, but you can also use covariance; if you have:

interface IAnimalCollection<out TValue> : IEnumerable<TValue>
{
    void AddNew();
}

and:

class AnimalCollection<TValue> : IAnimalCollection<TValue>
    where TValue : Animal, new()
{...}

then you can use:

static void foo(IAnimalCollection<Animal> zoo)
{
    foreach (var animal in zoo)
        Console.WriteLine(animal.Name);
    zoo.AddNew();
}

and your code:

var FishAquarium = new AnimalCollection<Fish>();
var MammalEnclosure = new AnimalCollection<Mammal>();

foo(FishAquarium);
foo(MammalEnclosure);

will work fine; but - this is making work for no reason - simple generics via the foo<T> method shown above is simpler and more direct.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1. My collection has new features which doesn't exists. 2. I don't want to use a generic function. I would like to create a non generic class which get a collection for work. – Syrlia Jul 17 '18 at 09:11
  • 2
    "I don't want to use a generic function" - so... you don't want to use the feature **specifically designed to handle this scenario** with the minimum of fuss? sigh; does your type implement `IEnumerable`? if so, `foo(IEnumerable zoo)` should work via the magic of covariance – Marc Gravell Jul 17 '18 at 09:14
  • Yes my collection implement IEnumerable. But IEnumerable doesn't contain the function "AddNew". So I can't use other interfaces. – Syrlia Jul 17 '18 at 09:17
  • 1
    @Syrlia I've added an edit to show how you can do the same with covariance without needing a generic method, but: *why* don't you want to use a generic method? what have you got against it? it addresses **exactly this problem** – Marc Gravell Jul 17 '18 at 09:22
  • This will work until you add the `Add(TValue value)` method to the `IAnimalCollection` interface. – mm8 Jul 17 '18 at 09:23
  • @mm8 indeed, which is why I keep trying to emphasize that the generic method `foo` approach is simpler - but: the requirements *as stated in the question* can be satisfied via a covariant interface, even if some hypothetical future requirements cannot – Marc Gravell Jul 17 '18 at 09:25
  • I tried to give a minimal example. In my real code the 'zoo' parameter is a class attribute. I don't want to make the class as a generic because it inherits from 'Window'. – Syrlia Jul 17 '18 at 09:27
  • 1
    @Syrlia nobody suggested making a new generic type (well, unless you count `IAnimalCollection`); a generic method is completely different to a generic type – Marc Gravell Jul 17 '18 at 09:33
  • Ok, then my example is to minimalistic. I'll update it. – Syrlia Jul 17 '18 at 09:37
0

Try this approach:

class Program
{
    static void Main(string[] args)
    {
        List<IAnimal> animals = new List<IAnimal>() { new Animal("Fuffy"), new Fish("Fishy"), new Mammal("Mommy") };
        OutputAnimalsNames(animals);
    }

    private static void OutputAnimalsNames(List<IAnimal> animals)
    {
        foreach (IAnimal animal in animals)
        {
            Console.WriteLine(animal.Name);
        }
    }
}

public interface IAnimal
{
    Guid Guid { get; }
    string Name { get; }
}

public class Animal : IAnimal
{
    public Guid Guid { get; private set; }
    public string Name { get; set; }

    public Animal(string name)
    {
        this.Name = name;
        this.Guid = Guid.NewGuid();
    }
}

public class Fish : Animal
{
    public Fish(string name) : base(name)
    {

    }
}

public class Mammal : Animal
{
    public Mammal(string name) : base(name)
    {

    }
}
Marco Salerno
  • 5,131
  • 2
  • 12
  • 32