Reputation: 940
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.
Upvotes: 0
Views: 124
Reputation: 8359
ref
for the list as the object is not copied when passed as parameter.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.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.
Upvotes: 1
Reputation: 152634
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).
Upvotes: 4