0

I'm trying to find the best way to tag properties of an object as required/optional/ignored.

public enum classtype
{
    A,
    B,
    C,
    D
}
class example
{
    public classtype type { get; set; }
    public string a { get; set; }
    public string b { get; set; }
    public string c { get; set; }
    public int d { get; set; }
}

What im doing so far:

public static void DoSomething(List<example> examples)

What i want is something like this:

public static void DoSomething(List<example> examples)
{
    foreach (example ex in examples)
    {
        foreach (PropertyInfo prop in ex.GetType().GetProperties())
        {
            if (prop.isRequired)
            {
                DoSomethingElse(prop);
            }
            else if (prop.isOptional)
            {
                DoThis(prop);
            }
            ...
        }
    
    }
}

i.e.

  • -type.A: a.req, b.opt, c.req, d.ign
    -type.B: a.ign, b.req, c.opt, d.req
    -...

The goal is to have a way to iterate over the objects and their properties.

I'm thinking to use a dictionary to define types, but that doesn't strike me as the most efficient way to implement this.

Rand Random
  • 7,300
  • 10
  • 40
  • 88
Paul_BK
  • 9
  • 3
  • Have a look at `DataMemberAttribute`'s `IsRequired` property. | https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datamemberattribute.isrequired?view=net-6.0 of https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datamemberattribute?view=net-6.0 – Rand Random Jul 22 '22 at 09:19
  • Have a look at this https://learn.microsoft.com/en-us/dotnet/api/system.attribute?view=net-6.0 – ShanieMoonlight Jul 22 '22 at 09:20
  • 3
    This feels like an [XY Problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What is the problem that you're trying to solve? – Hayden Jul 22 '22 at 09:20
  • Do you want to guarantee all required properties are set? Or validate objects according to some requirement? Or document your types as part of an API, like a json schema or similar? – JonasH Jul 22 '22 at 09:25
  • @Hayden I have multiple places where i have to iterate through objects of `example` So far i used switches based on the `type` attribute to grab the right properties. But that isn't scalable and every new type needs a lot of code modifications, which i want to avoid. – Paul_BK Jul 22 '22 at 09:33
  • any tips on editing the question are welcome, im new to the posting aspect on SO – Paul_BK Jul 22 '22 at 09:34
  • @Paul_BK Why do you have to iterate through these objects and why does it matter, which properties are required and which aren't? – xyldke Jul 22 '22 at 09:38
  • 2
    @Paul_BK Are you able to edit your question on what you're currently doing (i.e. the switch statement) so that we have a better idea of the context of the change you're wanting to do? To answer your question, you can "iterate" through properties via reflection, and you can use those rules to map to what you're wanting to do. We need more information though on what you're wanting to do though. – Hayden Jul 22 '22 at 09:44
  • @xyldke Im generating UI-elements, code, validating the objects and much more. For UI input i have to check different input fields depending on the type. Different Code gets generated for each type, etc. – Paul_BK Jul 22 '22 at 09:44
  • @Paul_BK - seems like you want to reimplement an property grid? have you considered using an existing one? eg. first google hit with "wpf propertygrid" https://www.codeproject.com/Articles/1092748/WPF-PropertyGrid-2 – Rand Random Jul 22 '22 at 09:52
  • or this: https://github.com/xceedsoftware/wpftoolkit/wiki/PropertyGrid – Rand Random Jul 22 '22 at 09:53
  • This might be too simplistic without knowing the bigger picture, but have you thought about creating custom attributes to decorate the properties with, passing in the relevant enum(s)? E.g. `[Required(ClassType.A | ClassType.B]`, `[Optional(ClassType.C | ClassType.D]`, etc. (You'd need to mark your enum with [Flags] to allow multiple values to be passed in using "|"). The attributes could be extended to include additional properties like validation messages, etc. – Andrew Stephens Jul 22 '22 at 10:25
  • @Hayden I added some examples. I hope that makes it more clear – Paul_BK Jul 22 '22 at 10:25
  • @AndrewStephens This looks like what i want. But Im not sure how to link the properties with the enum this way. – Paul_BK Jul 22 '22 at 10:59
  • Not too sure what you mean. The DoSomething() method would be similar to your second example (using reflection to retrieve the properties), with an extra step to retrieve the prop's custom attributes. If the prop has (say) a [Required] attribute, and one of the enum values passed to that attr matches the class's "type" property value, then you know that this property is "required" (in the context of that class type). – Andrew Stephens Jul 22 '22 at 11:12

2 Answers2

0

You could try using attributes.



class Example
{
    [UI(Type = UIType.Required)]
    public string a { get; set; }

    [UI(Type = UIType.Required)]
    public string b { get; set; }
}

then you can

PropertyInfo property = ...
var uis = property.GetCustomAttributes(typeof(UIAttribute), inherit: false) as UIAttribute[];
if (uis == null) ...
var ui = ui[0];
switch a 
{
   UIType.Required => ...
   ...
}
tymtam
  • 31,798
  • 8
  • 86
  • 126
0

The way that I would solve this is by making use of the Strategy Pattern

I'd transform your Example into something like:

public abstract class Example
{
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
    public int D { get; set; }

    public abstract void DoSomething();
}

The DoSomething method is where we want to do our heavy lifting. Derived classes can be created like the rules that was originally there, for example:

public class ExampleA : Example
{
    public override void DoSomething()
    {
        Console.WriteLine("Doing something A");
    }
}

public class ExampleB : Example
{
    public override void DoSomething()
    {
        Console.WriteLine("Doing something B");
    }
}

And now your code only has to do:

public static void DoSomething(List<Example> examples)
{
    foreach(Example ex in examples)
    {
        ex.DoSomething();
    }
}

If you're wanting to create Examples via your ClassType enum, you can create it via a factory like:

public class ExampleFactory
{
    private readonly List<Type> types = new();

    public ExampleFactory()
    {
        types.AddRange(Assembly.GetAssembly(typeof(Example))
                                ?.GetTypes()
                                .Where(t => t.IsSubclassOf(typeof(Example))) ?? Type.EmptyTypes);
    }
    
    public Example Create(ClassType type)
    {
        var typeToCreate = types.FirstOrDefault(t => t.Name.EndsWith(type.ToString()));
        if (typeToCreate == null)
        {
            throw new ArgumentException($"No type found for {type}");
        }
        return (Example)Activator.CreateInstance(typeToCreate)!;
    }
}

Which can be called like:

// Only create once
ExampleFactory factory = new ExampleFactory();
var example = factory.Create(ClassType.A);

The factory works based on a naming convention, where it has to have the suffix of the Class Type at the end (i.e. ExampleA), but this can be anything you want. You can modify the factory to accept constructor parameters.

If you're wanting to be adventurous (let me know if you want to see an example of this), you can change the factory to be source generated.

Hayden
  • 2,902
  • 2
  • 15
  • 29