Search code examples
genericsc#-7.0type-constraints

Consolidating 2 generic types into one for a generic function call


I'm sorry if the title is not very descriptive I have a hard time coming up with one.

I have an abstract baseclass with a generic type as one of its properties.

public abstract class AdapterBase<T>
{
        public string AdapterName { get; set; }
        public T? Configuration { get; set; }
}

I want to derive from the abstract class and pass along the type-information of the Configuration Property.

public class FooAdapter : AdapterBase<FooConfiguration>{}
public class BarAdapter : AdapterBase<BarConfiguration>{}

Both Configuration Objects have different properties, one might have a name, the other only a Guid. However Attached to each Configuration-class I have an attribute.

[MyCustomAttribute("Foo")]
public class FooConfiguration{}

[MyCustomAttribute("bar")]
public class BarConfiguration{}

Now I want to call a generic function that only checks for the existence of the attribute in the configuration field

public bool AdapterConfigurationHasMyCustomAttribute<T>()
{
  ....
}
...
main()
{
   bool isThere = AdapterConfigurationHasMyCustomAttribute<FooAdapter>(); => returns true
}

My problem is, if I don't specify that T is of AdapterBase then I have no access to any of the properties for T inside the function. So I have to constrain the type with a where clause

public bool AdapterConfigurationHasMyCustomAttribute<T>() where T : AdapterBase<U>
{
....
}

which would be fine for me. This solution, however, requires that I also supply the Type-Information for U which would make the signature look like this.

public bool AdapterConfigurationHasMyCustomAttribute<T,U>() where T : AdapterBase<U> where U: class, new()
{
....
}

preferably, however, I'd like to keep the signature with a single type since the type for U is already defined through by defining T. And adding another type would also mean that a call like this: main() { bool isThere = AdapterConfigurationHasMyCustomAttribute<FooAdapter, BarConfiguration>(); }

would result in True, even though FooAdapter and BarConfiguration don't belong together.

I could circumvent it by passing along an object as a parameter like this.

public bool AdapterConfigurationHasMyCustomAttribute<T>(AdapterBase<T> myAdapter) where T : class, new()
{
....
}

but instantiating an object when I only need information attached to a type seems overkill to me.

Likewise I would like to avoid a call like this where I pass the Configuration-Type along:

main()
{
   bool isThere = AdapterConfigurationHasMyCustomAttribute<FooConfiguration>();
}

This is because if someone else were to use the code they would need to have prior knowledge about which Configuration-Type belongs to which AdapterBase-derivative or else they might make mistakes later.

main()
{
   BarAdapter = new BarAdapter();
   bool isThere = AdapterConfigurationHasMyCustomAttribute<FooConfiguration>();
   if(isThere)
   {
     //doing something with BarAdapter after checking FooConfiguration
   }
}

I'd like for the method-signature to look like this.

 public bool AdapterConfigurationHasMyCustomAttribute<T>() where T : AdapterBase<U> where U: class, new()
    {
    ....
    }

Is there a way that allows me to avoid having to pass a second type while making sure T is always a derivative of AdapterBase?


Solution

  • you can achieve this by using reflection to inspect the attribute on T. modify your method like follow:

    public bool AdapterConfigurationHasMyCustomAttribute<T>() where T : AdapterBase<object>
    {
        Type configType = typeof(T).GetProperty("Configuration")?.PropertyType;
    
        if (configType != null)
        {
            var attribute = configType.GetCustomAttribute<MyCustomAttribute>();
            if (attribute != null && attribute.SomeProperty == "desiredValue")
            {
                return true;
            }
        }
        
        return false;
    }
    

    It constrains T to be a derivative of AdapterBase since you want to access the Configuration property, and its type is not known in advance.