简体   繁体   中英

How to add an item to a generic list?

Let me explain what I'm trying to accomplish. I have roughly twelve different lists of objects and twelve different methods to populate them all based on what they contain. I've been trying to re-factor the code to eliminate the, "wet" portions and get it down to the irreducible minimum.

Each of the twelve classes is very similar. Think of them like this. Some with a few more properties, some with less, and of various types.

class car
{
    public string brand_model { get; set; }
    public int numberOfDoors { get; set; }
    public string engineType { get; set; }
}

class bike
{
    public string brand_model { get; set;}
    public int numberOfMainCogs { get; set;}
    public int weight { get; set;}
}

I have three methods. The top method passes to the next method whichever list it's currently working with as a generic:

List<car> carList = new List<car>();
List<bike> bikeList = new List<bike>();

switch (vehicleType)
{
    case ("car"):
        Populate(ref carList);
        break;
    case ("bike"):
        Populate(ref bikeList);
        break;
 }

I call populate which just sends that empty list to a method that reads from a database.

public bool Populate<T>(ref List<T> vehicleList)
{
//figure out what kind of list was passed in
//do some stuff to connect to database
//call appropriate method to add items to that list
switch(vehicleType)
{
    case("car"):
        readCarInformation(reader, ref vehicleList);
        break;
    case("bike"):
        readBikeInformation(reader, ref vehicleList);
        break;
}

Let's say it was a car. Here's my method declaration.

private void readCarInformation(AseDataReader reader, ref List<car> carList)
    {
        while (reader.Read())
        {
            carList.Add(new car
            {
                brand_model = SafeGetInt(reader, 0),
                numberOfDoors = SafeGetInt(reader, 1),
                engineType = SafeGetString(reader, 2)
            });
        }
    }
}

I have tried multiple things now to get this to work. I'm trying to populate that carList that I made in my original method. What am I doing incorrectly?

Yes, I'm still a novice.

Typically when you have to use reflection to determine the "type" of a generic parameter, then do different things based on that type, you shouldn't be using generics. A better approach would be to have overloads for each type instead (factoring any common code that does not depend on reflection):

public bool Populate(ref List<car> carList)
{
    //do some stuff to connect to database
    AseDataReader reader = GetReader();

    readCarInformation(reader, ref carList);
}        
public bool Populate(ref List<bike> bikeList)
{
    //do some stuff to connect to database
    AseDataReader reader = GetReader();

    readBikeInformation(reader, ref bikeList);
}

The amount of code is roughly the same (swapping the overhead of the switch for multiple methods), it's compile-time safe, and it improves testability (you can test each Populate method separately rather than testing one Populate method with multiple cases).

I would also question if ref is necessary. if you're just adding to the list instance that's passed in, then ref is unnecessary. If you're assigning a new list, then out may be more appropriate (or use a List return type instead of a boolean).

  • You did not need to use ref for the list as the object is not copied when passed as parameter.
  • I think your readXXInformation should only read information, put the informations in a list is too specialized and should be performed elsewhere. Think that you may want to use it for other purpose.
  • Finally, you do not need to perform more than one switch.

Here a way to do it:

List<car> carList = new List<car>();
List<bike> bikeList = new List<bike>();

switch (vehicleType)
{
    case ("car"):
        carList.AddRange(readCarInformation(reader));
        break;
    case ("bike"):
        bikeList.AddRange(readBikeInformation(reader));
        break;
}

And your readers will looks like this:

private IEnumerable<Car> readCarInformation(AseDataReader reader)
{
    while (reader.Read())
    {
        yield return new car
        {
            brand_model = SafeGetInt(reader, 0),
            numberOfDoors = SafeGetInt(reader, 1),
            engineType = SafeGetString(reader, 2)
        };
    }
}

So yes, I removed your Populate function. If you really need to do more stuffs that just add the elements to the list, you can use a function like this:

private void Populate<T>(IList<T> destination, IEnumerable<T> source)
{
    destination.AddRange(source);
    ... // others stuffs.
}

And your switch will be:

switch (vehicleType)
{
    case ("car"):
        Populate(carList, readCarInformation(reader));
        break;
    case ("bike"):
        Populate(bikeList, readBikeInformation(reader));
        break;
}

[Edit]

You can also factorize the readXXInformation like this:

private IEnumerable<T> readObjectInformation<T>(AseDataReader reader, Func<AseDataReader, T> objectBuilder)
{
    while (reader.Read())
    {
        yield return objectBuilder(reader);
    }
}

And use small object builder like this one:

private Car carBuilder(AseDataReader reader)
{
    return new car
        {
            brand_model = SafeGetInt(reader, 0),
            numberOfDoors = SafeGetInt(reader, 1),
            engineType = SafeGetString(reader, 2)
        };
}

Finally you use it like this:

switch (vehicleType)
{
    case ("car"):
        carList.AddRange(readObjectInformation(reader, carBuilder));
        break;
    case ("bike"):
        bikeList.AddRange(readObjectInformation(reader, bikeBuilder));
        break;
}

or

private void Populate<T>(AseDataReader reader, IList<T> destination, Func<AseDataReader, T> objectBuilder)
{
    destination.AddRange(readObjectInformation(reader, objectBuilder));
    ... // others stuffs.
}

...

switch (vehicleType)
{
    case ("car"):
        Populate(reader, carList, carBuilder);
        break;
    case ("bike"):
        Populate(reader, bikeList, bikeBuilder);
        break;
}

There is a lot of solutions.

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