Search code examples
c#.nettypeconverter

TypeConverter with Interfaces


I'm having a little trouble implementing a property in C# as a designer type. Basically I have multiple concrete types that implement an interface. I'd like to select which concrete type at design-time.


using System;
using System.ComponentModel;
using System.Drawing;

/// <summary>
/// implement a summarizable type
/// </summary>
public interface ISummarizable
{
    /// <summary>
    /// Summarize along rows
    /// </summary>
    /// <param name="rawdata"></param>
    /// <returns></returns>
    double[] Summarize(double[,] rawdata);
}

/// <summary>
/// calculate by mean value
/// </summary>
public class MeanSummary : ISummarizable
{
    public double[] Summarize(double[,] rawdata)
    {
        double[] result = new double[rawdata.GetLength(0)];
        for (int n = 0; n < result.Length; n++)
        {
            double value = 0;
            for (int j = 0; n < rawdata.GetLength(1); j++)
                value += rawdata[n, j] / rawdata.GetLength(1);
            result[n] = value;
        }
        return result;
    }
}

/// <summary>
/// Calculate by max value
/// </summary>
public class MaxSummary : ISummarizable
{
    public double[] Summarize(double[,] rawdata)
    {
        double[] result = new double[rawdata.GetLength(0)];
        for (int n = 0; n < result.Length; n++)
        {
            double value = double.MinValue;     // guaranteed to change at least once
            for (int j = 0; n < rawdata.GetLength(1); j++)
                value = Math.Max(value, rawdata[n, j]);
            result[n] = value;
        }
        return result;
    }
}

/// <summary>
/// type converter
/// </summary>
public class SummaryTypeConverter : System.ComponentModel.TypeListConverter
{
    public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;
        return base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(string))
            return true;
        return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        if (value is string)
            return value.ToString();
        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            if(value is ISummarizable)
            {
                return value.GetType().Name;
            }
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }


    /// <summary>
    /// constructor w/ initializer
    /// </summary>
    public SummaryTypeConverter() :
        base(new Type[] { typeof(MeanSummary), typeof(MaxSummary) })
    {
    }
}

public class MyControl : System.Windows.Forms.Control
{
    protected ISummarizable _summary = new MaxSummary();

    [TypeConverter(typeof(SummaryTypeConverter))]
    public ISummarizable Summary { get { return _summary; } set { _summary = value; } }


}

In the property viewer I get a nice drop-down for the "Summary" property, with my two concrete types. But on selecting one the error is

Object of type System.String cannot be converted to type Example.ISummarizable

I've tried implementing ISummarizable in the ConvertFrom and ConvertTo but to no avail, and tried debugging it using a second Visual Studio instance, but I can't even trap this error. Any thoughts?


Solution

  • In your ConvertFrom implementation, in the case you receive a string that makes sense (ex. "MaxSummary"), you need return an instance of that actual type, instead of calling the base method (as Carsten said).

    You could try to do that using reflection (find the type with that name, check if it is ISummarizable, call the constructor), or using a switch statement.

    With reflection, your code would be:

    public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        if (value is string)
        {
            var source = (string)value;
            var sourceType = Type.GetType(source);
    
            if (sourceType != null && typeof(ISummarizable).IsAssignableFrom(sourceType))
            {
                var constructor = sourceType.GetConstructor(Type.EmptyTypes);
                var sourceInstance = constructor.Invoke(new object[0]);
    
                return sourceInstance;
            }
        }
    
        return base.ConvertFrom(context, culture, value);
    }