0

I would appreciate your help. I have been struggling with this for way too long.

I could easily get around this by creating two separate model classes with a duplicate list of properties, but I'd like to learn how to achieve this with the base class. So, an IntSensor and ExtSensor are both a Sensor. From another class I parse a json file with all of the data and that works great. But, trying to call a method that returns a List<Sensor> baseclass and trying to cast it as either subclass is killing me. What am I doing wrong... or is there a better way? Thank you!

PS: this may look like a duplicate question, but I tried the other solutions and they are marked in the `Repository class' as "//seen on other StackOverflow Posts -- fails" in the code.

//Model classes

public class Device
{
    public string IP { get; set; }   
    public string Name { get; set; }
    public IList<IntSensor> InternalSensors { get; set; }
    public IList<ExtSensor> ExternalSensors { get; set; }
}

public class Sensor
{
    public string Name { get; set; }
    public string ActualTemp { get; set; }
    public string HighTemp { get; set; }
    public string LowTemp { get; set; }
}

public class IntSensor : Sensor {}
public class ExtSensor : Sensor {}

//Business Class -- parsing json

public class ParseJsonData
{
    public static RoomAlertModel GetRoomAlertModel(JObject jsonTree)
    {
        RoomAlertModel model = new RoomAlertModel();
        model.IP = jsonTree["ip"].ToString();
        model.Name = jsonTree["name"].ToString();

        return model;
    }

    public static List<Sensor> GetSensors(JToken jToken)
    {
        var sensors = new List<Sensor>();

        try
        {
            foreach (var item in jToken)
            {
                var s = new Sensor();
                s.Label = item["lab"].ToString();
                s.ActualTemp = item["tf"] != null ? item["tf"].ToString() : "";
                s.HighTemp = item["hf"] != null ? item["hf"].ToString() : "";
                s.LowTemp = item["lf"] != null ? item["lf"].ToString() : "";

                sensors.Add(s);
            }
        }
        catch (Exception)
        {
        }            

        return sensors;
    }
}

//Repository

public class RoomAlertRepository
    {
        internal RoomAlertModel Retreive()
        {
            var filePath = HostingEnvironment.MapPath(@"~/App_Data/RoomAlertsData.json");

            var json = File.ReadAllText(filePath);

            JObject jsonTree = JObject.Parse(json);
            var internalSensorTree = jsonTree["internal_sen"];
            var externalSensorTree = jsonTree["sensor"];

            var model = ParseJsonData.GetRoomAlertModel(jsonTree);

            var baseList = ParseJsonData.GetSensors(internalSensorTree);

            //seen on other StackOverflow Posts -- fails
            var iSensorsTry1 = baseList.Cast<IntSensor>();
            //seen on other StackOverflow Posts -- fails
            var iSensorsTry2 = baseList.ConvertAll(instance => (IntSensor)instance);
            //seen on other StackOverflow Posts -- fails
            var iSensorsTry3 = baseList.OfType<IntSensor>();

            model.InternalSensor = iSensorsTry1.ToList();


            return model;
        }
    }
Chris
  • 118
  • 1
  • 9
  • You are running into "generic type variance". A `List` simply is not compatible with a `List`. For example, given a `List`, you're allowed to put an `ExtSensor` into it. Then if you were allowed to cast to `List`, now you've got a list that's supposed to only have `IntSensor` elements but has an `ExtSensor` element. – Peter Duniho Jan 20 '18 at 04:54
  • Note that as far as the three attempts in your code go: you can't cast the objects you have. `Cast()` will throw an exception and `OfType()` just won't return anything. You _can_ use `ConvertAll()` though, but your lambda needs to create new objects, not attempt a cast from `Sensor` to e.g. `IntSensor`. See second marked duplicate for information about that aspect of your question. – Peter Duniho Jan 20 '18 at 05:04
  • I disagree that this is a duplicate question. Try any of the solutions from the other posts. I don't think that they work. One answer was list.Type of... doesn't work. The other answers didn't work either. It's easy to answer these threads with a "duplicate" answer, but make sure that those duplicate answers work first. I might be wrong, but I don't think so. Thanks for your reply though. – Chris Jan 20 '18 at 05:52
  • Besides isn't that the basis of inheritance: ExternalSensor "is a" Sensor and InternalSensor "is a" Sensor? I thought that I could cast it to a derived type? – Chris Jan 20 '18 at 06:04
  • 1
    The other answer "don't work" only in the sense that you didn't apply them correctly. The basis of your question assumes you are creating `Sensor` objects. nothing in the question suggested you were okay with just creating a different type of object in the first place; you are _specifically asking about casting_. _"isn't that the basis of inheritance"_ -- no, not in the way you are trying to use it. Inheritance means that an instance of a derived class "is a" instance of the base class; but it's not a two-way street. – Peter Duniho Jan 20 '18 at 06:14
  • Thanks for the explanation. I'll work on this topic as I'm trying to get my code cleaner and more OO. Thanks again! – Chris Jan 20 '18 at 06:20

1 Answers1

1

In your GetSensors method, you are creating instances of the base class Sensor. Trying to implicitly cast them to some other derived class is not possible. You should use a generic type parameter in that method and create instances of the actual derived type.

Like so: (notice the where clause and the new constraint of the T parameter)

public static List<T> GetSensors<T>(JToken jToken) where T : Sensor, new()
{
    var sensors = new List<T>();

    try
    {
        foreach (var item in jToken)
        {
            var s = new T();
            s.Label = item["lab"].ToString();
            s.ActualTemp = item["tf"] != null ? item["tf"].ToString() : "";
            s.HighTemp = item["hf"] != null ? item["hf"].ToString() : "";
            s.LowTemp = item["lf"] != null ? item["lf"].ToString() : "";

            sensors.Add(s);
        }
    }
    catch (Exception)
    {
    }            

    return sensors;
}

Now call it like this:

List<IntSensor> iSensors = ParseJsonData.GetSensors<IntSensor>(internalSensorTree);
GregorMohorko
  • 2,739
  • 2
  • 22
  • 33
  • Thank you Greg! I tried using generics, but could not get it right. I'm unable to test it since I am at home, but will tomorrow or Monday. I hope this doesn't get deleted since it was marked as a duplicate question (I don't think it was a duplicate). Thanks again!! – Chris Jan 20 '18 at 05:13
  • Greg, I also tried an implicit cast and that didn't seem to work. Can you give an example. I can't imagine that I got that wrong too. Thanks again! – Chris Jan 20 '18 at 05:15
  • 1
    Hmm, I just tried it out and it seems I was wrong: you cannot solve this problem with implicit conversions. `user-defined conversions to or from a derived class are not allowed` – GregorMohorko Jan 20 '18 at 05:24
  • Thank goodness. I was about ready to give up on everything if I couldn't have conquered that. Haha. I can't believe I had that much trouble. Thanks again! – Chris Jan 20 '18 at 05:41