开发者

Func<> using base class as parameter

开发者 https://www.devze.com 2023-03-05 00:54 出处:网络
I have a bunch of开发者_StackOverflow中文版 methods which I want to check some metadata of, all of them have different parameters but the parameters all derive from BaseClass.

I have a bunch of开发者_StackOverflow中文版 methods which I want to check some metadata of, all of them have different parameters but the parameters all derive from BaseClass.

public void CheckMethod(Func<BaseClass, Object> func)
{
    // Check method metadata here
}

public Object MethodToCheck(DerivedClass foo)
{
    // Whatever...
}

public void Test()
{
    CheckMethod(MethodToCheck);
}

Code fails on CheckMetadata(MethodToCheck), becuase MethodToCheck has a DerivedClass as parameter and not a BaseClass. I've tried using generics:

public void CheckMethod<T>(Func<T, Object> func)

...

CheckMethod<DerivedClass>(MethodToCheck);

I would like the call to CheckMethod to be as little to type as possible and would prefer to only call it with CheckMethod(MethodToCheck). Is this possible?


Check out the MSDN page on covariance and contravariance. The article seems to suggest that you cannot achieve this in prior versions.

In case you are stuck with an older version, I would suggest that you explore the visitor pattern.


How about something like

public void CheckMethod<T>(Func<T, Object> func) where T : BaseClass


This appears to be something the C# language designers just didn't think of. What you (and I) are looking for is a compile-time "Interface" for Funcs/Delegates, so they can essentially be used in a collection.

Another way of asking your question is how do I constrain someone passing an array like this:

new[] {
    new Func<MaxLengthAttribute, string>(a => "maxlength=" + a.Length),
    new Func<RequiredAttribute, string>(a => "required")
}

To only pass Funcs of the format Func<out Attribute, string>? Should be simple... no?

No. As the other answer linked to, the out keyword isn't allowed on input parameters like the one we're using here.

If you must have the constraint at compile-time you're stuck with Java-like ugly acrobatics:

public interface IHasMethod<T>
    where T : BaseClass
{
    void MethodToCheck<T>(T, object);
}

public class Class1 : IHasMethod<DerivedClass>
{
    public object MethodToCheck(DerivedClass d)
    {

    }
}

Meaning your test looks like:

public void CheckMethod(IHasMethod methodHaver)
{
}

Eugh. Likewise my array example becomes: new IHasMethod[]

If you prefer prettier, more flimsy code that is only protected/inspected at run-time your alternative is Delegate. Your signature:

public void CheckMethod(Delegate func)

My array example:

new Delegate[] ...

But in both cases you're in danger because that Delegate's format is unbounded at compile-time - it's now on you to do what would normally be the compiler's problem, checking the right number of parameters and their types before proceeding. If you can, try to make that an ugly check in debug code only and any calls to the method extracted and exposed to Unit Tests so even though you've lost compile-safety, you replace it with automated testing safety. Like:

#if DEBUG
        foreach(var attrTempl in attributeTemplates)
        {
            var templParams = attrTempl.Method.GetParameters();
            if (templParams.Length != 1)
                throw new Exception("Can't have " + templParams.Length + " params in AttributeTemplate Delegate, must be Func<out Attribute, string>");

            var type1 = templParams[0].ParameterType;
            var type2 = attrTempl.Method.ReturnType;

            if (!type1.IsSubclassOf(typeof(System.Attribute)))
                throw new Exception("Input parameter type " + type1.Name + " must inherit from System.Attribute");

            if (type2 != typeof(string))
                throw new Exception("Output parameter type " + type2.Name + " must be string");
        }
#endif

One last helper for hurdles in this: If you take the Delegate approach, you'll convert code that looks like this:

Func<A, B> fn

To this:

Delegate fn

You probably at some point call that Func, like:

var b = fn(a);

Which will now get a compile error, because the compiler accurately asserts it has no idea if this Delegate accepts any parameters. Lovely. You can punt past it to run-time with:

var b = (B)fn.DynamicInvoke(a);

Related

0

精彩评论

暂无评论...
验证码 换一张
取 消