7

Targeting .NET 4.0, I'm trying to build the following classes:

public class ConfigurationElementCollection<TElement, TParent> 
    where TElement : ParentedConfigElement<TParent>, new() 
    where TParent : class
{
    public TParent ParentElement { get; set; }

    protected ConfigurationElement CreateNewElement()
    {
        //**************************************************
        //COMPILER GIVES TYPE CONVERSION ERROR ON THIS LINE!
        //**************************************************
        return new TElement { ParentCollection = this };
    }
}

public class ParentedConfigElement<TParent> : ConfigurationElement where TParent : class
{
    internal ConfigurationElementCollection<ParentedConfigElement<TParent>, TParent> 
        ParentCollection { get; set; }

    protected TParent Parent
    {
        get
        {
            return ParentCollection != null ? ParentCollection.ParentElement : null;
        }
    }
}

As the code comment above indicates, the compiler gives an error:

Cannot implicitly convert type 'Shared.Configuration.ConfigurationElementCollection<TElement, TParent>' to 'Shared.Configuration.ConfigurationElementCollection<Shared.Configuration.ParentedConfigElement,TParent>

I don't expect this error, because the compiler also knows that TElement is a Shared.Configuration.ParentedConfigElement<TParent> due to the generic type constraint I specified.

Still, I figure I will expressly cast the type to get past this issue:

(ConfigurationElementCollection<ParentedConfigElement<TParent>,TParent>) this;

Unfortunately, I get the same compiler error. Why is this happening? What did I do wrong? And without resorting to dynamic types, what can I do to fix this?

Brent Arias
  • 29,277
  • 40
  • 133
  • 234

2 Answers2

4

Based on https://stackoverflow.com/a/6529618/5071902

protected ConfigurationElement CreateNewElement()
{
    return (TElement)Activator.CreateInstance(typeof(TElement), this);
}

You will need a constructor with this signature, setting the ParentCollection property.

You can try using reflection too. Take a look at this answer https://stackoverflow.com/a/6529622/5071902

Community
  • 1
  • 1
Renan Araújo
  • 3,533
  • 11
  • 39
  • 49
  • The Activator.CreateInstance is only needed when there is no default constructor on the target object. In my example, I do indeed have a default constructor. It is only a property that I'm initializing...not a constructor parameter. – Brent Arias Sep 11 '15 at 23:19
2

Your problem is that you have a type CEC<A, B> and you trying to assign it to a property of type CEC<C<A>,B> which you can't do, in much the same way as List<string> cannot be assigned to storage of type List<object> even though string derives from object.

A clean solution without using implicit operators or dynamic is to use an interface:

public interface IConfigurationElementCollection<TParentedConfig, TParent> 
    where TParentedConfig : ParentedConfigElement<TParent>
    where TParent : class
{
    TParent ParentElement { get; }
}

public class ConfigurationElementCollection<TElement, TParent> : IConfigurationElementCollection<ParentedConfigElement<TParent>, TParent>
    where TElement : ParentedConfigElement<TParent>, new() 
    where TParent : class
{
    public TParent ParentElement { get; set; }

    protected ConfigurationElement CreateNewElement()
    {
        //**************************************************
        //COMPILER NO LONGER GIVES TYPE CONVERSION ERROR 
        //BECAUSE this IMPLEMENTS THE EXPECTED INTERFACE!
        //**************************************************
        return new TElement { ParentCollection = this };
    }
}

public class ParentedConfigElement<TParent> : ConfigurationElement where TParent : class
{
    internal IConfigurationElementCollection<ParentedConfigElement<TParent>, TParent> 
        ParentCollection { get; set; }

    protected TParent Parent
    {
        get
        {
            return ParentCollection != null ? ParentCollection.ParentElement : null;
        }
    }
}

Since the property in ParentedConfigElement is internal, you can also make the interface internal to avoid exposing this implementation detail to any consumers, if that sort of thing is a concern for you.

Erik
  • 5,355
  • 25
  • 39
  • It is obnoxious that the generic constraint on CEC connotes exactly the same thing as the interface you provided...and yet the generic constraint feels as though it is being ignored. Still, I'm very happy you found a way to make this work without using a `dynamic` type. – Brent Arias Sep 12 '15 at 05:13
  • But the generic constraint doesn't say the exact same thing as the interface, it just says that the first type argument inherits from `ParentedConfigElement`. This means that `TElement` is assignable to `ParentedConfigElement` but that inheritance doesn't extend to the type parameters of generic types. – Erik Sep 14 '15 at 15:47