Koray
Koray

Reputation: 1796

Cannot convert object to its type

I have a function that has an input of type object. The value that comes to this function is an array of a class called 'Box' I've made the following tries but not able to convert the object value to Box[].

    internal class BoxConverter : IRunDataConverter
    {
        string IRunDataConverter.Key => "boxes";

        object IRunDataConverter.ConvertToPublishable(object value)
        {
            //return value;

            object[] arr = (object[])value;//value is an array of Box

            System.Diagnostics.Debug.WriteLine(arr[0].GetType());// --> WF.CV.Entity.Box
            System.Diagnostics.Debug.WriteLine(typeof(Box));//      --> WF.CV.Entity.Box

            System.Diagnostics.Debug.WriteLine(arr[0].GetType().AssemblyQualifiedName);// --> WF.CV.Entity.Box, WF.CV, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
            System.Diagnostics.Debug.WriteLine(typeof(Box).AssemblyQualifiedName);//      --> WF.CV.Entity.Box, WF.CV, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null 

            bool sameTypes = typeof(Box) == arr[0].GetType();// FALSE



            Box[] try1 = arr.OfType<Box>().ToArray();// try1 -> array with zero lenght.

            Box[] boxes = new Box[arr.Length];
            for(int i=0; i<arr.Length; i++)
            {
                Box box = (Box)arr[i]; // Error:
                                       // System.InvalidCastException:
                                       // '[A]WF.CV.Entity.Box cannot be cast to
                                       //  [B]WF.CV.Entity.Box. Type A originates from 'WF.CV, Version=1.0.0.0,
                                       //           Culture=neutral, PublicKeyToken=null' in the context 'Default' at
                                       //           location 'C:\_koray\korhun\WF\WF.Web\bin\Debug\net5.0\WF.CV.dll'.
                                       //  Type B originates from 'WF.CV, Version=1.0.0.0,
                                       //           Culture=neutral, PublicKeyToken=null' in the context 'Default' in a byte array.'




                dynamic val = new System.Dynamic.ExpandoObject();
                val.Name = box.Name;
                val.Confidence = box.Confidence;
                val.Coords = box.Coords;
                boxes[i] = val;
            }
            return boxes;
        }
    }

When I type typeof(Box) and arr[0].GetType() into Immediate Window I get the following result. (Unseen parts in the picture are same.) enter image description here

This is the Box class:

using Newtonsoft.Json;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
    
namespace WF.CV.Entity
{
    [DebuggerDisplay("Name: {Name} Confidence: {Confidence} Location: {Location}")]
    public class Box
    {
        public string Name { get; set; }
        public float Confidence { get; set; }
    
        /// <summary>
        /// Normalized MinX, MinY, MaxX, MaxY
        /// </summary>
        public float[] Coords { get; set; }
    
        [JsonIgnore]
        public float Left => Coords[0];
        [JsonIgnore]
        public float Right => Coords[2];
        [JsonIgnore]
        public float Top => Coords[1];
        [JsonIgnore]
        public float Bottom => Coords[3];
    
        [JsonIgnore]
        public float Width => Coords[2] - Coords[0];
        [JsonIgnore]
        public float Height => Coords[3] - Coords[1];
    
    
        public Rect GetRect(int width, int height)
        {
            int x = (int)(Coords[0] * width);
            int y = (int)(Coords[1] * height);
            int w = (int)((Coords[2] - Coords[0]) * width);
            int h = (int)((Coords[3] - Coords[1]) * height);
            return new Rect(x, y, w, h);
        }
    
    }
}

This happens inside an unsafe dll being used by an Entity Framework and Web API projects, in Visual Studio 2019, Windows 10 environment. All projects' target framework is .NET 5.0.

Update - What happens before:

IRunDataConverter is being called with this manager class. It's in a non-unsafe dll:

public class RunDataConverterManager : IRunDataConverterManager
{
    private readonly ILogger<IRunDataConverter> logger;
    private readonly IServiceProvider provider;

    public RunDataConverterManager(ILogger<IRunDataConverter> logger, IServiceProvider provider)
    {
        this.logger = logger;
        this.provider = provider;
        initialize();
    }

    private Dictionary<string, IRunDataConverter> converters;
    private void initialize()
    {
        this.converters = new Dictionary<string, IRunDataConverter>();

        string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        foreach (string dll in Directory.GetFiles(path, "*.dll"))
            AppDomain.CurrentDomain.Load(File.ReadAllBytes(dll));

        var type = typeof(IRunDataConverter);
        var types = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(s => s.GetTypes())
            .Where(p => type.IsAssignableFrom(p) && p.IsClass);

        HashSet<string> hs = new();
        foreach (Type ty in types)
            if (hs.Add(ty.AssemblyQualifiedName))
            {
                IRunDataConverter converter = null;
                try
                {
                    converter = create(ty);
                }
                catch (Exception ex)
                {
                    this.logger.LogError($"Cannot create IRunDataConverter! assemblyName: {ty.AssemblyQualifiedName} typeName: {ty.Name} exception: {ex.GetLogMessage()}");
                }
                if (converter != null)
                {
                    if (this.converters.ContainsKey(converter.Key))
                        throw new Exception($"Duplicated key for IRunDataConverter! There can be only one converter for a key. Key: {converter.Key}");
                    else
                        this.converters.Add(converter.Key, converter);
                }
            }
    }
    private IRunDataConverter create(Type ty)
    {
        //ref: https://stackoverflow.com/a/34746583/1266873
        return (IRunDataConverter)ActivatorUtilities.CreateInstance(this.provider, ty);
    }



    IRunDataPublishable[] IRunDataConverterManager.ConvertToPublishable(RunData[] data)
    {
        return this.getData(data).ToArray();
    }
    private IEnumerable<IRunDataPublishable> getData(RunData[] data)
    {
        Dictionary<string, List<object>> dic = new();
        if (data != null)
            foreach (RunData item in data)
                if (item != null)
                {
                    //this is where it's called
                    object value = this.converters.ContainsKey(item.Key) ? this.converters[item.Key].ConvertToPublishable(item.Value) : item.Value;
                    if (!dic.ContainsKey(item.Key))
                        dic.Add(item.Key, new List<object>() { value });
                    else
                        dic[item.Key].Add(value);
                }
        foreach (string key in dic.Keys)
            yield return new runData()
            {
                Key = key,
                Values = dic[key].ToArray()
            };
    }
    private class runData : IRunDataPublishable
    {
        public string Key { get; set; }
        public object[] Values { get; set; }
    }
}


//RunData class is in another non-unsafe dll:
[DebuggerDisplay("Key: {Key} Value: {Value}")]
    public class RunData
    {
        public RunnerNode Node { get; set; }
        public string Key { get; set; }
        public object Value { get; set; }

        public RunDataDictionary Children { get; private set; } = new RunDataDictionary();

        public RunData(RunnerNode node, string key, object value)
        {
            this.Node = node; 
            this.Key = key;
            this.Value = value;
        }

        public RunData Clone()
        {
            return new RunData(this.Node, this.Key, this.Value)
            {
                Children = this.Children.Clone()
            };
        }
    }

IRunDataConverterManager is a singleton injected item (Startup/ConfigureServices/ services.AddSingleton<IRunDataConverterManager, RunDataConverterManager>();)

RunData items, the values are not serialized-deserialized in any way, until the issue I'm facing in the BoxConverter.ConvertToPublishable method.

Upvotes: 2

Views: 977

Answers (2)

turgay
turgay

Reputation: 458

Please change the code below.

AppDomain.CurrentDomain.Load(File.ReadAllBytes(dll)) -> AppDomain.CurrentDomain.Load(Assembly.LoadFrom(dll).GetName());

Upvotes: 1

Slate
Slate

Reputation: 3694

Good to see you resolved the issue.

Regarding your update with the extra code -- I'd be really cautious about using the Assembly GetTypes() and the Activator for it. It's always better to create instances of types you have static access to rather than going through reflection or breaking interface rules. I believe GetTypes() returns new type instances, and since Type .Equals/== only checks reference of the underlying system type, then a new instance will not be equal.

I wonder if your problem is the two Box types look the same but the instances are different, then the runtime is not able to convert the two.

Upvotes: 1

Related Questions