简体   繁体   中英

How can I iterate over an List<T> to access values when T is returned from a Factory and unknown

Please forgive me if the title is not worded correctly.

I am retrieving data from database tables for various devices and building a list. Different devices could have the same properties and will definitely have some properties that differ, So I am using a Factory Pattern to create whichever is needed at run time.

Factory class:

public interface IImportModel
{
    IList CreateImportList(SqlDataReader reader);
}

And Concrete class:

public class Device1ImportModel : IImportModel
{
    public string SerialNumber { get; set; }
    public string PartA { get; set; }

    public IList CreateImportList(SqlDataReader reader)
    {
        Device1ImportModel linkedItem = new Device1ImportModel();

        List<Device1ImportModel> importList = new List<Device1ImportModel>();

        while (reader.Read())
        {
            linkedItem = new Device1ImportModel();

            linkedItem.SerialNumber = reader["SerialNo"].ToString();
            linkedItem.PartA = reader["PartA"].ToString();

            importList.Add(linkedItem);
        }

        return importList;
    }
}

I create the device from the factory:

importModel = ImportModelFactory.CreateImportModel("Device1");

Now when I want to iterate over the importModel, I receive a compile time error on the line where I attempt to access item.SerialNumber

foreach (var item in importList)                  
{
      string number = item.SerialNumber;
}

The error:

'object' does not contain a definition for 'SerialNumber' and no extension method 'SerialNumber' accepting a first argument of type 'object' could be found.

If I place a breakpoint and hover over item variable, I can see the properties and value.

I tried using dynamic instead of var, but then later in my code I can no longer use Linq or Lambda queries.

  1. How can I access the values?
  2. Can I convert the IList to List perhaps using Reflection?

Edit 1

Added Code for CreateImportModel:

static public IImportModel CreateImportModel(DeviceType device)
{
    switch (device)
    {
        case DeviceType.Device1:
            return new Device1ImportModel();

        case DeviceType.Device2:
            return new DeviceImportModel();

        default:
            return null;
    }
}

If you cannot change your method's signature, you can use:

foreach (var item in importList.Cast<Device1ImportModel>())                  
{
    string number = item.SerialNumber;
}

This will throw an exception, however, if there will be an object in the importList collection that is not a Device1ImportModel or its derived class.

If you're not sure that all objects in the list are of that type and want to avoid exceptions, use this apporach:

foreach (var item in importList.OfType<Device1ImportModel>())                  
{
    string number = item.SerialNumber;
}

Change IList to List<Device1ImportModel> (or IList<Device1ImportModel> or IReadOnlyList<Device1ImportModel> ).

public List<Device1ImportModel> CreateImportList(SqlDataReader reader)

IList is an older interface (pre-generics) and thus if you use IList (rather than IList<Device1ImportModel ) then the compiler / runtime has no notion of the Type of your data (ie it treats it as object ), thus:

'object' does not contain a definition for 'SerialNumber' and no extension method 'SerialNumber' accepting a first argument of type 'object' could be found.

You may also need to change the interface to:

public interface IImportModel<T>
{
    List<T> CreateImportList(SqlDataReader reader);
}

and the class to:

public class Device1ImportModel : IImportModel<Device1ImportModel>
{
    public string SerialNumber { get; set; }
    public string PartA { get; set; }

    public List<Device1ImportModel> CreateImportList(SqlDataReader reader)
    {

You likely also want to change CreateImportModel so instead of calling it like:

ImportModelFactory.CreateImportModel("Device1");

you instead call it like:

ImportModelFactory.CreateImportModel<Device1ImportModel>();

so that a concrete Device1ImportModel is returned (and thus SerialNumber is accessible).

I ended up just using a separate class to hold all the properties, whether each other concrete class uses them or not.

Its not ideal but works.

I would have preferred relying on each concrete class to be responsible for it's own properties though.

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