Generics and TypeConverters

Posts   
 
    
Posts: 21
Joined: 18-Sep-2006
# Posted on: 24-Oct-2006 16:42:56   

I need to be able to programmatically set up TypeConverters to convert certain numeric database fields to Enum-based types. The program doing this knows which fields need to map to which enum. My problem is that LLBLGen checks object types quite rigorously, so I can't just change a few properties and fool it into doing the necessary casts.

The possible approaches that occurred to me were: 1. Use reflection to auto-generate an assembly containing a TypeConverter for each enumeration used. 2. Write one TypeConverter that claimed to be able to convert any number to any enum, then wrap it in a TypeConverterDefinition that supported the appropriate claim. LLBLGen's output code usually puts a cast somewhere on the TypeConverter's output anyway. 3. Write one generic TypeConverter that takes a type parameter to determine which enum it does conversion for.

This is related to the uber-Data-Tier-Generator I'm working on, so writing one TypeConverter per enum by hand isn't practical; we might end up with two hundred different enums at some point. For similar reasons, including bulk, generating lots of TypeConverters isn't practical either.

I initially tried option 2, but it seems that the code would check the object's type when the Entity was saved and complain if it wasn't originally the correct type, regardless of casts applied. So option 2 doesn't work.

Option 3 seemed fine, until the compiler complained of unexpected characters. LLBLGen doesn't deal with generic type parameters; it dumps them in the format seen in Type.FullName, with the backticks and the square brackets. I can fix the generated code with a regex, but I figured I'd ask if there were any patches for this yet.

Are there any plans to support generic parameters like this?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 25-Oct-2006 10:26:37   

Type converters work with one core type, and that type is determined by createinstance. This thus means that you can't have a typeconverter with multiple types.

I don't really fully understand the problem you discuss with point 3. The type comparison internally in the typeconverter is up to you. Where exactly do you see the generic backtic stuff appear?

Frans Bouma | Lead developer LLBLGen Pro
Posts: 21
Joined: 18-Sep-2006
# Posted on: 25-Oct-2006 11:06:59   

Actually, you can have a TypeConverter with multiple types, if you create one TypeConverterDefinition for each type you want to use the TypeConverter with, and set CoreType after you set ConverterInstance. smile I realise this is not supposed to be done, however. stuck_out_tongue_winking_eye

I wanted a single TypeConverter class that worked with a type determined by a parameter. This was necessary because I needed to support an arbitrary number of Int -> (enum type) conversions, one for each enumeration used as a field type.

Passing the enum's type as an ordinary parameter breaks the requirement for TypeConverters to have a default constructor. The other option was generics; using a generic type parameter, the full description of the TypeConverter (including the enum it worked with) could be stored in its Type.

Here's the EnumTypeConverter I'm using:


    public class EnumTypeConverter<E> : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            ...
            // return true for anything that can be produced from an Enum.
            ...
        }
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            ...
            // return true for anything that can converts to an Enum.
            ...
        }
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if(CanConvertFrom(context, value.GetType()))
            {
                switch(Type.GetTypeCode(value.GetType()))
                {
                    case TypeCode.String:
                        return Enum.Parse(typeof (E), (string)value);
                        
                    default:
                        return Enum.ToObject(typeof(E), value);
                }
            }
            else
            {
                throw new NotSupportedException(String.Format("Conversion from a value of type '{0}' to '{1}' isn't supported.", value.GetType().ToString(), typeof(E).Name));
            }
        }
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if(CanConvertTo(context, destinationType))
            {
                switch (Type.GetTypeCode(value.GetType()))
                {
                    case TypeCode.String:
                        return value.ToString();

                    default:
                        return Convert.ChangeType(value, Type.GetTypeCode(destinationType));
                }
            }
            else
            {
                throw new NotSupportedException(String.Format("Conversion to a value of type '{0}' from '{1}' isn't supported.", destinationType.ToString(), typeof(E).Name));
            }
        }
        public override object CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
        {
            Array values = Enum.GetValues(typeof (E));
            return Enum.ToObject(typeof (E), values.GetValue(values.GetLowerBound(0)));
        }
    }

The uber-DDL app sets up TypeConverterDefinitions for each enumeration defined, applies them to the appropriate fields, then passes the whole lot on to LLBLGen.

LLBLGen will happily generate the code and doesn't complain about the TypeConverter being a generic type, since all it cares about is that it has the TypeConverter's Type fullname to play with. This fullname looks something like this:

[EnumTypeConverter`1[[ActionType, (various other stuff here)]], (various other stuff here)]

and I guess the code generation works by grabbing the type name from the fullname, resulting in the following appearing in PersistenceInfoProvider.cs:

new EnumTypeConverter`1[[ActionType, (various other stuff here)]]()

It's not a problem anymore; I fixed it by processing the file prior to compilation with the following code:


        private Regex _rxBadGenericType = new Regex(@"`(?<count>[0-9]+)\[(?<params>\[[a-zA-Z0-9.]+.*?\](,(?=\[))?)+\]",
                                                    RegexOptions.Compiled | RegexOptions.ExplicitCapture);
        private Regex _rxGetGenericType = new Regex(@"\[(?<class>[a-zA-Z0-9.]+),.*?\]", RegexOptions.Compiled);
        private string _rrFixGenericType = "${class}";
        private string[] _patchGenericsInFiles = {
                                                     @"DatabaseSpecific\PersistenceInfoProvider.cs"
                                                 };
        protected void DoGenericTypeFixup()
        {
            foreach(string fileToPatch in _patchGenericsInFiles)
            {
                string fileName = Path.Combine(ProjectDir, fileToPatch);
                File.Move(fileName, fileName + ".backup");
                using(TextReader reader = new StreamReader(fileName + ".backup"))
                {
                    using(TextWriter writer = new StreamWriter(fileName))
                    {
                        string line;
                        while((line = reader.ReadLine()) != null)
                        {
                            writer.WriteLine(_rxBadGenericType.Replace(line, FixGenericType));
                        }
                    }
                }
            }
        }
        
        protected string FixGenericType(Match match)
        {
            int count = Convert.ToInt32(match.Groups["count"].Value);
            string param = _rxGetGenericType.Replace(match.Groups["params"].Value, _rrFixGenericType, count);
            return String.Format("<{0}>", param);
        }

I guess fixing LLBLGen to support generics like this would be more trouble than it's worth; supporting generic parameters to TypeConverters in the GUI would seem to be nigh on impossible, and I'm probably the only one trying to do this sort of odd stuff stuck_out_tongue_winking_eye . I'm not sure how the tasks work but I'd guess that creating a Type object from the fullname and extracting generic parameters would be awkward. A Regex-based approach like the one above would probably work, but would need to be applied differently for different languages.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 26-Oct-2006 12:06:54   

It would indeed be a problem to support that, as the type converter wouldn't be a single, static piece of code, but would change according to the type you specify WITH the typeconverter.

Interesting idea though, however IMHO a little bit too cumbersome to implement, as the maintenance code and requirements from the user are above what I find acceptable.

Frans Bouma | Lead developer LLBLGen Pro