Home > Mobile >  How can I create a List(of T) where T is an interface of (T) and the contents of the List inherit an
How can I create a List(of T) where T is an interface of (T) and the contents of the List inherit an

Time:11-10

I am attempting to implement some generic config types and have bumped into an issue with conversions. Ideally what I'd like is a strongly typed instance of configuration values that can be assigned to our different Exporters.

What I have is:

  • IConfigurableExport: Interface to mark exporter as configurable and contains a ICollection<IExportConfiguration<Object>> Configurations property
  • IExportConfiguration<T>: Interface to define common properties (Name, Description, etc.)
  • ExportConfiguration<T>: Abstract class that implements IExportConfiguration<T>, sets the interface properties as Abstract, adds some common functionality like setting the value of itself from a list of configuration values and whatnot
  • ConfigurationInstance<bool>: Inherits ExporterConfiguration<Boolean>

The UI to configure the exporters is dynamically built based on the values from IConfigurableExport.Configurations. When I try to call IConfigurableExport.Configurations I get a casting error. Here is some sample code to illustrate the problem:

using System;
using System.Linq;
using System.Collections.Generic;
                    
public class Program
{
    public static void Main()
    {
        var t = new MyExporter();
        var configs = t.Configurations;
        foreach(var conf in configs){
            Console.WriteLine(conf.Name);
        }
    }
}

public interface IExportConfiguration<T>{ 
    string Description {get;}
    Guid Id {get;}
    string Name {get;}
    T Value {get; set;}
}


public abstract class ExportConfiguration<T>
    : IExportConfiguration<T>
{
    public abstract string Description { get; }
    public abstract string Name { get; }
    public abstract Guid Id { get; }
    public abstract T Value { get; set; }
    public abstract T DefaultValue { get; set; }
    
    public virtual void SetValue(ICollection<KeyValuePair<Guid, object>> values)
    {
        if (values.Any(kvp => kvp.Key == this.Id))
            Value = (T)values.First(kvp => kvp.Key == this.Id).Value;
        else
            Value = this.DefaultValue == null ? this.DefaultValue : default(T);
    }
}

public interface IConfigurableExport {
    ICollection<IExportConfiguration<object>> Configurations {get;}
}

public class MyConfig : ExportConfiguration<bool> {
    public override string Description { get { return "This is my description"; }}
    public override string Name { get{ return "My Config Name"; }}
    public override Guid Id { get { return Guid.Parse("b6a9b81d-412d-4aa8-9090-37c9deb1a9f4"); }}
    public override bool Value { get; set;}
    public override bool DefaultValue { get; set;}
}

public class MyExporter : IConfigurableExport {
    MyConfig conf = new MyConfig();
    public ICollection<IExportConfiguration<object>> Configurations {
        get {
            return new List<IExportConfiguration<object>> { (IExportConfiguration<object>)conf };
        }
    }
}

Here is a dontnetfiddle to illustrate the problem

CodePudding user response:

The main problem is that IExportConfiguration<T> defines a T Value {get; set;}

If you have an IExportConfiguration<bool>, you can't cast IExportConfiguration<bool> to an IExportConfiguration<object> because you can't set Value with a type of object, it has to be set with a type of bool in order for it to be type safe.

To be able to make that kind of cast you would need to use a covariant interface like below

public interface IExportConfiguration<T> : IReadOnlyExportConfiguration<T>{
    new T Value {get; set;}
}

//this interface is covariant, and if you have a IReadOnlyExportConfiguration<string>
//it can be cast to an IReadOnlyExportConfiguration<object>
public interface IReadOnlyExportConfiguration<out T>{ 
    string Description {get;}
    Guid Id {get;}
    string Name {get;}
    T Value {get;}
}

and then you can redefine your configurable export as

public interface IConfigurableExport {
    ICollection<IReadOnlyExportConfiguration<object>> Configurations {get;}
}

But then you can't use MyConfig as a bool config because IReadOnlyExportConfiguration<bool> will not cast to IReadOnlyExportConfiguration<object> because bool is a simple data type. The type of T has to be a class type in order for it to be covariant with object.

public class MyConfig : ExportConfiguration<string> {
    public override string Description { get { return "This is my description"; }}
    public override string Name { get{ return "My Config Name"; }}
    public override Guid Id { get { return Guid.Parse("b6a9b81d-412d-4aa8-9090-37c9deb1a9f4"); }}
    public override string Value { get; set;}
    public override string DefaultValue { get; set;}
}

And then finally your exporter becomes

public class MyExporter : IConfigurableExport {
    MyConfig conf = new MyConfig();
    public ICollection<IReadOnlyExportConfiguration<object>> Configurations {
        get {
            return new List<IReadOnlyExportConfiguration<object>> { (IReadOnlyExportConfiguration<object>)conf };
        }
    }
}

But at this point you won't be able to set values on your configurations, and if that's needed, then you will need to restructure your solution.

  • Related