0

I'm making controls for my winform program, and this error happens when saving the test form.

error


This is the solution structure, the controls library is seperated in 1 project and another is a test project contains a test form.

Solution
├ Test (Test Project)
│ └ Form1
└ WinFormControls (Library Project)
  └ ImageButton (UserControl)

The control has a TypeConverter attached, here is brief code.

To make the question simple, I omit other methods, you can read it from this link

public class StateConverter : ExpandableObjectConverter
{
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if(destinationType == typeof(InstanceDescriptor))
        {
            var ctor = typeof(State).GetConstructor(new Type[] { typeof(int), typeof(Image) });
            if (ctor != null)
            {
                var state = (State)value;
                return new InstanceDescriptor(ctor, new object[] { state.GetData(), state.Image });
            }
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }
}

The error is not always happens here are steps to reproduce:

  1. Open the solution in VS
  2. Open the Form1 designer, drag an ImageButton from toolbox
  3. Make some change in ImageButton.cs (eg. add a space), then rebuild the solution
  4. Return to the Form1 designer, make some change with NormalState property in the properties window, then save, the error occurs.
  5. Since then if you make change on the ImageButton and save, the error will show, even if drag another ImageButton from the toolbox, except reopen the VS.

After some debug, I found the error occurs with this line:

var state = (State)value;

At first I guessed the value is null, so I added some log with it:

try {
    var state = (State)value;
} catch (Exception ex) {
    File.AppendAllText("errorlog.txt", ex.ToString() +
       (value == null ? "NULL" : value.GetType().ToString());
}

Finally I got:

System.InvalidCastException: Specified cast is not valid. at         WinFormControls.ImageButton.StateConverter.ConvertTo ...... WinFormControls.ImageButton+State

So the value is not null, and the type of value is exactly what I cast to.


Update

Output AssemblyQualifiedName, IsAssignableFrom, is:

value.GetType().AssemblyQualifiedName;
typeof(State).AssemblyQualifiedName;
typeof(State).IsAssignableFrom(value.GetType());
value.GetType().IsAssignableFrom(typeof(State))
value is State
ReferenceEquals(value.GetType(), typeof(State))

Strange result:

WinFormControls.ImageButton+State, WinFormControls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
WinFormControls.ImageButton+State, WinFormControls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
False
False
False
False

Two questions:

  1. Why the error happens?
  2. How can I avoid it during developing?
shingo
  • 18,436
  • 5
  • 23
  • 42
  • It does seem rather strange... Are you absolutely sure that (value == null || typeof(State) == value.GetType())? – Eric McLachlan Mar 10 '19 at 09:05
  • 1
    It's painful, but I guess you need to close VS and clear the designer assembly cache. Take a look at [this post](https://stackoverflow.com/a/54724628/3110834). Another thing which you need to consider is checking `if (value is State)` in `ConvertTo`, however it will not solve the problem but it's usually needed in type converters. – Reza Aghaei Mar 10 '19 at 09:31
  • 1
    @EricMcLachlan see the catch code, I'm sure value is not null (State is a struct so in theory it also won't be null) and an update to my question, I try to output the `AssemblyQualifiedName` of the 2 types, em.. they are identical. – shingo Mar 10 '19 at 09:45
  • Very strange indeed. I wonder if the struct is being boxed in an object and that is causing the problem. Would you consider changing the struct to a class and evaluating the difference? – Eric McLachlan Mar 10 '19 at 09:54
  • If so, perhaps typeof(State).IsAssignableFrom(value.GetType()) == false whereas typeof(value.GetType()).IsAssignableFrom(State) == true? – Eric McLachlan Mar 10 '19 at 09:56
  • 1
    @EricMcLachlan the behaviour between class and struct in the designer are almost different, hard to change, but can be a choice. `typeof(value.GetType()).IsAssignableFrom(State)` is also false, seems these 2 types are different at all, maybe a cache problem. – shingo Mar 10 '19 at 10:04
  • 1
    Thanks @RezaAghaei check `is State` is helpful. – shingo Mar 10 '19 at 10:42
  • If you want to test the controls in your class library properly (i.e. full debug support), I suggest that you read [Walkthrough: Debugging Custom Windows Forms Controls at Design Time](https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/walkthrough-debugging-custom-windows-forms-controls-at-design-time) and setup your project as described in that article as described under the heading: "Setting Up the Project for Design-Time Debugging". Then only edit the test form in the debugging session. – TnTinMn Mar 11 '19 at 03:57

1 Answers1

2

This is because of the different way that type converter and designer loads the type. Type converter uses CLR cache to load the type while designer uses type resolutions service. So when you change the class library project and build it again, the CLR loads the assembly from previously cached version of the type in CLR, while designer loads it from newly built assembly and it results in a type mismatch.

To solve the problem, in the class library project:

  1. Open AssemblyInfo.CS and change assembly version attribute to change the build number of the assembly in each build and save the file. This forces the CLR to invalidate its cache and load the new type from the new version of assembly:

    [assembly: AssemblyVersion("1.0.0.*")]

  1. Right click and unload the class library project, then right click and edit the project file and change the project deterministic build to false:

    <Deterministic>false</Deterministic>

The problem is already addressed in this forum post.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • I assume you have already applied `if (value is State)` in `ConvertTo`. – Reza Aghaei Mar 10 '19 at 16:32
  • Before applying the solution, you may want to close the solution and clean VS designer cache as I described [here](https://stackoverflow.com/a/54724628/3110834). FYI, I applied the solution successfully on the project which you've shared. – Reza Aghaei Mar 11 '19 at 03:25
  • If for any reason you need to debug the designer, take a look at [this post](https://stackoverflow.com/a/37347549/3110834). – Reza Aghaei Mar 11 '19 at 08:15
  • Hello @Reza Aghaei again. You are really my man again. Thanks one god you are here. Please I need to know is this issue by default for visual studio? I take long day and would hit my head at wall.... The first time TypeConverter works awesome (Converter was one from your posts...) But when modify it second time. third,etc it never works until restart visual studio. So Is This is only way to add Deterministic? is there any side-effects, performance issues when build or... – deveton Jun 02 '21 at 07:37
  • 1
    Hi @deveton. I'm not pretty sure about the possible side-effects, but another possible way to get around this, may be putting the type converter in a different assembly than the user control. – Reza Aghaei Jun 02 '21 at 09:13