![]() | Steema Issues DatabaseNote: This database is for bugs and wishes only. For technical support help, if you are a customer please visit our online forums;otherwise you can use StackOverflow. Before using this bug-tracker we recommend a look at this document, Steema Bug Fixing Policy. |
| Summary: | Deserialization fail on user customized classes which derive from TeeChart classes | ||
|---|---|---|---|
| Product: | .NET TeeChart | Reporter: | cqb <cqb> |
| Component: | Importing | Assignee: | Steema Issue Manager <issuemanager> |
| Status: | VERIFIED INVALID | ||
| Severity: | blocker | CC: | chris |
| Priority: | --- | ||
| Version: | unspecified | ||
| Target Milestone: | --- | ||
| Hardware: | PC | ||
| OS: | Windows | ||
| Chart Series: | --- | Delphi / C++ Builder RAD IDE Version: | |
| Attachments: | Deserialization bug in TeeChart | ||
|
Description
cqb
2014-04-11 05:09:47 EDT
The problem here is that when the TeeChart serialization code searches for a type, it does search for the type in all libraries referenced by the ExecutingAssembly, as well as in all libraries referenced by the EntryAssembly, but what it doesn't do is recursively search through all assemblies referenced by those assemblies, which is in fact your case. The code we're running looks like this:
private Type FindType(string typeName)
{
Type result = InternalFindType(typeName, GetType().Assembly);
if (result == null)
result = InternalFindType(typeName, Assembly.GetExecutingAssembly());
if (result == null)
result = InternalFindType(typeName, Assembly.GetEntryAssembly());
if (result == null)
{
foreach (AssemblyName a in Assembly.GetExecutingAssembly().GetReferencedAssemblies())
{
result = InternalFindType(typeName, Assembly.Load(a));
if (result != null)
{
break;
}
}
}
if (result == null)
{
foreach (AssemblyName a in Assembly.GetEntryAssembly().GetReferencedAssemblies())
{
result = InternalFindType(typeName, Assembly.Load(a));
if (result != null)
{
break;
}
}
}
return result;
}
This gives you an easy workaround - instead of your AppFail project referencing CallingLib, it can reference MyLib meaning FormFail would look like this:
private void Form1_Load(object sender, EventArgs e)
{
//CallingClass cc = new CallingClass();
//cc.AddLines(tChart1);
AddLines(tChart1);
}
public void AddLines(TChart tChart)
{
Double[] xValues = new Double[] { 1, 2, 3, 4, 5, 6, 7 };
Double[] yValues = new Double[] { 2, 5, -3, 7, 0, 5, 2 };
MyLine myLine = new MyLine();
myLine.Add(xValues, yValues);
tChart.Series.Add(myLine);
}
This means that your MyLine type is found and serialization occurs successfully. Of course the issue here is putting a limit on the amount of recursive searching done by our FindType method - we could change it to search the assemblies referenced by those referenced, but then why not search for the assemblies referenced by those etc.?
In fact, I see that what I suggest is exactly what your AppOK project does. This particular case has now been fixed, with recursion set to two levels, e.g.
private Type FindType(string typeName)
{
Type result = InternalFindType(typeName, GetType().Assembly);
if (result == null)
result = InternalFindType(typeName, Assembly.GetExecutingAssembly());
if (result == null)
result = InternalFindType(typeName, Assembly.GetEntryAssembly());
return result;
}
private Type InternalFindType(string typeName, Assembly a)
{
Type[] types = a.GetExportedTypes();
foreach (Type t in types)
{
if (t.FullName == typeName)
return t;
}
foreach (Type t in GetReferencedTypes(a))
{
if (t.FullName == typeName)
return t;
}
return null;
}
//CDI only two levels of recursion here, as more quickly leads to StackOverflow due to possibility
//of circular references e.g. System.Configuration->System->System.Configuration
private List<Type> GetReferencedTypes(Assembly a)
{
List<Type> types = new List<Type>();
foreach (AssemblyName name in a.GetReferencedAssemblies())
{
Assembly aa = Assembly.Load(name);
types.AddRange(aa.GetExportedTypes());
foreach (AssemblyName aname in aa.GetReferencedAssemblies())
{
Assembly aaa = Assembly.Load(aname);
types.AddRange(aaa.GetExportedTypes());
}
}
return types;
}
Two levels can fix the issue in the demo project only, which can't fulfill the requirements in practice. In large projects, the calling levels may be greater than two.
I have a suggest: let users participate in the searching by override or event, because the users must know how to find the type they have defined. see below:
1. override. Make FindType(string typeName) protected method, thus the user can override the search
protected virtual Type FindType(string typeName)
{
...
}
2. expose an event.
public delegate Type UserFindTypeDelegate(string typeName)
public event UserFindTypeDelegate UserFindType;
private Type FindType(string typeName)
{
if(UserFindTypeDelegate != null)
{
Type result = UserFindTypeDelegate(typeName);
if(result != null) return result;
}
Type result = InternalFindType(typeName, GetType().Assembly);
if (result == null)
result = InternalFindType(typeName,
Assembly.GetExecutingAssembly());
if (result == null)
result = InternalFindType(typeName,
Assembly.GetEntryAssembly());
return result;
}
thus, the problem is fixed and the risk of stack flow issue caused by search level is avoided, and no much code change is needed, what do you think of this idea?
I liked the event idea, unfortunately I don't think it's going to work. The problem is that during template import both the TChart and Chart object instances are overwritten by the call to BinaryFormatter.Deserialize(), meaning that the delegate reference of the event is lost. Overriding the FindType method is not going to be feasible either, due to the structure of the TeeChart API, in the sense that doing so would require a great deal of 'boilerplate' code to get it working. I'm not sure quite what the answer is here. By far and away the easiest workaround is to move necessary references up the reference chain so they are visible to TeeChart deserialization, although I understand this might not always be neither possible nor desirable. |