0

I have these classes

public class SubMenuItem : SubMenuVariant
{
    public string SubMenuTitle { get; set; }

    public LinkFieldType Link { get; set; }
   
    public List<SubMenuSubItem> SubItems { get; set; }
}

public class SubMenuHighlightItem : SubMenuVariant
{
    [JsonPropertyName(FieldNames.HighlightTitle)]
    public string HighlightTitle { get; set; }

    [JsonPropertyName(FieldNames.HighlightText)]
    public string HighlightText { get; set; }

    [JsonPropertyName(FieldNames.HighlightText)]
    public Link HighLightLink { get; set; }

}

public class SubMenuVariant
{
}

Which I currently store in a List<SubMenuVariant> submenu

Problem is though I am not able to access the individual properties the different menues have, since they are being casted to a SubMenuVariant, which don't have any properties.

The list can only contain one type, at no point will both types exist in the list. The items is not being added explicitly to the list, but is being created by JsonSerializer.Deserialize a json request, which contains the properties, to the baseclass.

So the json can either look like this:

{
   "submenu": [
   {
    "SubMenuTitle " : "Title", 
    "Link" : "Link", 
    "SubItems" : [
       {...}
    ]   
   }
   ]
}  

Or

 {
   "submenu": [
   {
    "HighlightTitle " : "Title", 
    "HighlightLink" : "Link", 
    "HighlightText" : "Text"
   }
   ]
} 

Is it somehow possible to store different class types in the same list?

I am not Fat
  • 283
  • 11
  • 36

3 Answers3

3

Your issue is not that you can't store different types derived from the same base class. Your problem is accessing the members of the run-time types of the objects. That requires a cast. You can conditionally cast the items as you get them out of the list:

foreach (var smv in submenu)
{
    var smi = smv as SubMenuItem;

    if (smi != null)
    {
        // ...
    }
    else
    {
        var smhi = smv as SubMenuHighlightItem;

        if (smhi != null)
        {
            // ...
        }
    }
}

In newer versions of C#, you can use pattern-matching:

foreach (var smv in submenu)
{
    if (smv is SubMenuItem smi)
    {
        // ...
    }
    else if (smv is SubMenuHighlightItem smhi)
    {
        // ...
    }
}

Here's an example of the pattern-matching option in action:

class Program
{
    static void Main(string[] args)
    {
        var items = new List<BaseType>();

        items.Add(new FirstDerivedType { FirstName = "One" });
        items.Add(new SecondDerivedType { SecondName = "Two" });
        items.Add(new FirstDerivedType { FirstName = "Three" });
        items.Add(new SecondDerivedType { SecondName = "Four" });

        foreach (var bt in items)
        {
            if (bt is FirstDerivedType fdt)
            {
                Console.WriteLine(fdt.FirstName);
            }
            else if (bt is SecondDerivedType sdt)
            {
                Console.WriteLine(sdt.SecondName);
            }
        }
    }
}

public class FirstDerivedType : BaseType
{
    public string FirstName { get; set; }
}

public class SecondDerivedType : BaseType
{
    public string SecondName { get; set; }
}

public class BaseType
{
}
jmcilhinney
  • 50,448
  • 5
  • 26
  • 46
  • I like this approach, but it does not seem to be pattern-matched? I assume once it is casted there is no way back? – I am not Fat Oct 20 '22 at 11:12
  • 1
    @IamnotFat, you seem to be under some misconceptions about what casting is and isn't. casting doesn't change the object in anyway. It's the same object with the same members, regardless of the type of the variable you assign it to. You can assign a `Button` object to an `object` variable, a `Control` variable and a `Button` variable and it's still the same object. Each variable only exposes the members of its own type, but the other members are still there. – jmcilhinney Oct 20 '22 at 11:34
  • hmm... Then I don't understand why I am not able to cast it to a specific object, The list is returned by a method, and when I loop through it, as try to patternmatch it, it doesn't seem to be captured in any of the if statement? – I am not Fat Oct 20 '22 at 11:38
  • You can update your question with all the relevant code but I have added an example that demonstrates pattern-matching. – jmcilhinney Oct 20 '22 at 11:42
  • I modified it a bit, I don't explicitly add the items to the list, but the content of the list is created by JsonSerializer.Deserialize a json string. The json itself contains the value. – I am not Fat Oct 20 '22 at 12:11
  • Another important point is that both types will not appear in the list, only one of the other type will appear. – I am not Fat Oct 20 '22 at 12:15
  • Added a bit more to the question to elaborate on the issue of being able to deserialize json to the object it matches. – I am not Fat Oct 20 '22 at 12:24
  • So the issue i had seem to have disapperared over the night?.. it works just as you suggested hurray! – I am not Fat Oct 21 '22 at 09:32
0

No, your solution is as good as it gets. The only other - worse - option being List<object>.

Oliver Weichhold
  • 10,259
  • 5
  • 45
  • 87
0

You can also try reflection, if you know the property name you can access it as follows:

 internal class Program
{
    static void Main(string[] args)
    {
        List<SubMenuVariant> variants = new List<SubMenuVariant>();

        variants.Add(new Sub1() { Title = "Test" });
        variants.Add(new Sub2());


        var prop = variants.First().GetType().GetProperty("Title");

        prop?.GetValue(variants.First(), null);


    }

}
public class Sub1 :SubMenuVariant
{
    public string Title { get; set; }   
}

public class Sub2: SubMenuVariant
{
    public int Index { get; set; }
}
public class SubMenuVariant
{
}

This will generate the following result: enter image description here

DonMiguelSanchez
  • 376
  • 3
  • 15