Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid Cast of Type Constrained C# Generic

Tags:

c#

.net

generics

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>,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?

like image 903
Brent Arias Avatar asked Sep 11 '15 02:09

Brent Arias


People also ask

What is constraints in c#?

Constraints inform the compiler about the capabilities a type argument must have. Without any constraints, the type argument could be any type. The compiler can only assume the members of System.Object, which is the ultimate base class for any .NET type. For more information, see Why use constraints.

What is type of c#?

C# provides a standard set of built-in types. These represent integers, floating point values, Boolean expressions, text characters, decimal values, and other types of data. There are also built-in string and object types. These types are available for you to use in any C# program.

What is the purpose of the class constraints on a type parameter?

Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type. They declare capabilities that the type argument must have, and must be placed after any declared base class or implemented interfaces.


2 Answers

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

like image 86
Renan Araújo Avatar answered Sep 17 '22 19:09

Renan Araújo


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.

like image 42
Erik Avatar answered Sep 18 '22 19:09

Erik