简体   繁体   中英

Multiple generic types in same list and calling their methods

I'm making an object validation framework in my spare time to learn a few things and maybe use it for some school projects.

I have my generic Rule class, which looks something like this :

class Rule<T>
{
    string propertyName;
    Func<T, bool> ruleLambda;

    bool IsBroken(T value)
    {
        return ruleLambda(value);
    }
}

An object that would be validated would look a bit like this :

class Example
{
    List<Rule<?>> MyRules; // can take all types of rules

    List<Rule<T>> Validate<T>(string propertyName, T value)
    {
        List<Rule<T>> brokenRules = new List<Rule<T>>();
        foreach (Rule rule in MyRules.Where(r => r.propertyName == propertyName))
        {
            if (rule.IsBroken(value))
                brokenRules.Add(rule);
        }
        return brokenRules;
    }
}

Where the T value argument would be the value of one of the Example class's properties, which can be of any type.

The Validate<T> method is called whenever a property is set.

The problem lies with the class's list of rules. Specifically the List<Rule<?>> line above. I want to store all the rules for a given class in the same list.

Alas, C# doesn't have a wildcard for generic types like in Java.

How should I do this?

A non-generic interface or base class utilizing objects instead of T could work, but how would I call the generic Rule's IsBroken method and not the non-generic one?

I would store your rules as object inside the Example class and use Enumerable.OfType<T> to find the matching rules for a given type:

class Example
{
    private List<object> rules;

    List<Rule<T>> Validate<T>(string propertyName, T value)
    {
        return this.rules.OfType<Rule<T>>()
            .Where(r => r.PropertyName == propertyName && r.IsBroken(value))
            .ToList();
    }
}

In cases where I've needed something like this, I use interfaces or non-generic base classes. For example, you could create an interface:

public interface IRule
{
  //non-generic properties & methods
}

public class Rule<T> : IRule
{
  //implementation
}

then create a list of the interfaces:

private List<IRule> MyRules;

If you want to make converting from the interface to the generic easy, you could add an extension method:

public static Rule<T> ToGeneric<T>(this IRule rule)
{
  return rule as Rule<T>;
}

I've tried a few things and I've found something that works pretty well for my needs. I have Rule<T> inherit from a base abstract rule class, with a generic IsBroken method:

abstract class Rule
{
    string propertyName;
    Func<object, bool> objectRule;

    bool IsBroken<T>(T value)
    {
        Rule<T> rule = this as Rule<T>;
        if (rule == null)
            return objectRule(value);

        return rule.IsBroken(value);
    }
}

As you can see, I try to convert the base class to its generic counterpart using the generic type parameter in the IsBroken method.

Also, when creating a Rule<T> instance, I send a Func<object, bool> to its base class protected constructor:

public Rule(string propertyName, Func<T, bool> ruleLambda)
    : base(propertyName, ConvertToObjectFunc(ruleLambda))
{
}

With the conversion method looking like this:

static Func<object, bool> ConvertToObjectFunc(Func<T, bool> func)
{
    return new Func<object, bool>(o => func((T)o));
}

However, if it can't cast o to type T, it crashes. So I wrote this... thing:

static Func<object, bool> ConvertToObjectFunc(Func<T, bool> func)
{
    return new Func<object, bool>
    (
        o =>
        {
            try
            {
                T obj = (T)o;
                return func(obj);
            }
            catch { return true; } // rule is broken by default
        }
    );
}

It's pretty ugly, but it works. Hope this can help anybody else.

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