20

I'm trying to cast an object of a certain type to an interface it implements using Convert.ChangeType(), however an InvalidCastException gets thrown because the object must implement IConvertible.

The types:

public IDocumentSet : IQueryable {}

public IDocumentSet<TDocument> : IDocumentSet, IQueryable<TDocument> {}

public XmlDocumentSet<TDocument> : IDocumentSet<TDocument> {}

Excerpt from code where the error happens:

private readonly ConcurrentDictionary<Type, IDocumentSet> _openDocumentSets = new ConcurrentDictionary<Type, IDocumentSet>();

public void Commit()
{
    if (_isDisposed)
        throw new ObjectDisposedException(nameof(IDocumentStore));

    if (!_openDocumentSets.Any())
        return;

    foreach (var openDocumentSet in _openDocumentSets)
    {
        var documentType    = openDocumentSet.Key;
        var documentSet     = openDocumentSet.Value;

        var fileName        = GetDocumentSetFileName(documentType);
        var documentSetPath = Path.Combine(FolderPath, fileName);

        using (var stream = new FileStream(documentSetPath, FileMode.Create, FileAccess.Write))
        using (var writer = new StreamWriter(stream))
        {
            var documentSetType     = typeof (IDocumentSet<>).MakeGenericType(documentType);
            var writeMethod         = typeof (FileSystemDocumentStoreBase)
                                        .GetMethod(nameof(WriteDocumentSet), BindingFlags.Instance | BindingFlags.NonPublic)
                                        .MakeGenericMethod(documentSetType);
            var genericDocumentSet  = Convert.ChangeType(documentSet, documentSetType); <-------

            writeMethod.Invoke(this, new[] {writer, genericDocumentSet});
        }
    }
}

Now, I'm failing to understand why exactly this happens (as XmlDocumentSet is not a value type) and XmlDocumentSet<'1> implements IDocumentSet<'1>. Am I missing something? Or is there an easier way to achieve what I'm doing?

artganify
  • 683
  • 1
  • 8
  • 23
  • If you use `Convert.ChangeType()` then the source object must implement `IConvertible` - _"For the conversion to succeed, value must implement the IConvertible interface, because the method simply wraps a call to an appropriate IConvertible method."_ [(documentation)](https://msdn.microsoft.com/en-us/library/dtb69x08(v=vs.110).aspx) – stuartd Sep 16 '16 at 11:33
  • @stuartd I was under the impression that `IConvertible` is only required for value types. And I can't cast the values because I don't know the type during compile time. – artganify Sep 16 '16 at 11:35
  • 1
    Even if you don't know the types used in this part at runtime, you still would have the types defined somewhere, correct? Define them with the `IConvertible`, with the proper conversion process to all the types you would have them converted to. – Chuck Savage Sep 16 '16 at 19:27

2 Answers2

3

The IConvertible interface is designed to allow a class to safely convert itself to another Type. The Convert.ChangeType call uses that interface to safely convert one type to another.

If you do not know the Types at compile time then you will be forced to attempt a runtime cast. This is discussed in a very similar question here Convert variable to type only known at run-time?.

Community
  • 1
  • 1
PhillipH
  • 6,182
  • 1
  • 15
  • 25
3

Implementing IConvertible is a lot of pain for such legitimate scenarios, and in my opinion waste of precious development time. Best is to implement an abstract method in the base class, which your derived class will implement to return itself. below is the example.

//implement this in base class
        protected abstract BaseDocumentTypeMap<ID> ConvertDocType(T doc);

//usage of the abstract code
            BaseDocumentTypeMap<ID> beDocType;

                //loop through all the document types and check if they are enabled
                foreach(T doc in result)
                {
                    beDocType = ConvertDocType(doc);
                    //some action
                }


//implement this in the derived class
        protected override BaseDocumentTypeMap<int> ConvertDocType(DocumentTypeMap doc)
        {
            return doc;
        }

This work perfectly and without a need of painful IConvertible. in the above example the base class is implementing an interface with <ID, T> and derived class has a reference to the DocumentTypeMap class, and DocumentTypeMap class is implementing the interface with <ID>

Kalpesh Popat
  • 1,416
  • 14
  • 12
  • Although I didn't try your method, but thank you for warning me about how painful this is – Philip Apr 17 '20 at 00:22
  • Anyone have any idea why System.Convert wouldn't just return the instance when the target type is a base type (i.e. IsAssignableFrom) of the instance? It seems deadset on using IConvertible when it literally already has an instance of the type it's trying to "convert" to. If we're using a higher layer of .NET code that uses System.Convert we're chained to some buried secret knowledge that the object must implement IConvertible. Microsoft.Maui.Controls is introducing code now is depends on it. – Josh Sutterfield Oct 19 '22 at 17:35