2

I have an interface IApiDataWithProperties. A class called Event implements this interface.

Normally I would be able to cast an object of IApiDataWithProperties to Event (assuming it is one) and for the compiler to let me do that no problem.

In this case, the type is actually a generic TApiData which has a where restriction to interface IApiDataWithProperties. However, I am unable to cast TApiData to Event even with the type restriction. I get Cannot convert type 'TApiData' to 'Event'

Why is this? Am I missing something?

public class Event : IApiDataWithProperties, IXmlSerializable
{
    // ...
}


public abstract class AbstractBatchPropertyProcessor<TApiData> : AbstractBatchProcessor<TApiData>, IBatchProcessor
    where TApiData : IApiDataWithProperties
{
    protected virtual string Build(ConcurrentBag<TApiData> batch)
    {
        foreach (var newItem in batch)
        {   
           if(newItem is Event)
           {
              // This cast fails: Cannot convert type 'TApiData' to 'Event'
              ((Event)newItem).Log();
           }
        }

        // ...  
    }
}

Edit:

I'm just looking to know why this is a compilation error.

I'm aware this is a strange design, and you wouldn't normally cast like that inside a generic method anyway. This was something I came across when I wanted to add some quick logging info there during a test, and this was the path of least effort.

AndySavage
  • 1,729
  • 1
  • 20
  • 34

5 Answers5

3

The reason is that newItem can be any type that implements IApiDataWithProperties, so compiler can't guarantee that it's type is convertible to Event. Even if you check it using is operator it means nothing to the compiler. As a workaround you can use double cast:

((Event)(object)newItem).Log();

Even though this works, it doesn't mean you should use it. You shouldn't check the type in a generic method. Instead try using polymorphism, add the Log method to IApiDataWithProperties or some other interface and implement it in your types. Then have another constraint for that interface, then you can call the method without needing a cast.

Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • @KonradKokosa maybe it's because you added the explanation later. :) – Selman Genç Nov 27 '14 at 23:00
  • 2
    Hard times here on SO sometimes... ;) – Konrad Kokosa Nov 27 '14 at 23:01
  • I still don't understand. For example, I can still do (this would throw an exception, but will compile): `IApiDataWithProperties foo = new NotEvent(); var evt = (Event)foo`. Why is that not a compilation error yet `TApiData` is? Surely the compiler has the same information. – AndySavage Nov 27 '14 at 23:04
  • 1
    @AndySavage because TApiData is more general than IApiDataWithProperties, it can be any type that implements your interface. So the type is not known at _compile time_.But, in your example the compiler knows the compile-time type which is `IApiDataWithProperties` and allows you to cast it explicitly.Ofcourse it doesn't consider the underlying type, which is why you need explicit cast. – Selman Genç Nov 27 '14 at 23:12
2

Short version - you can use:

(newItem as Event).Log();

instead of your cast.

Long version - your TApiData objects implements IApiDataWithProperties so it can be Event, but it can be everything else implementing it. This is called downcasting and must be done at runtime with help of as/is operators. Compiler does not know during compilation if newItem of generic type is really an Event so it cannot assure such cast.

Konrad Kokosa
  • 16,563
  • 2
  • 36
  • 58
  • You should never use an `as`-style cast without checking for null, otherwise you're just changing a `TypeCastException` for a `NullReferenceException`. There is no difference between an `as` cast and a `(Type)` cast with an `is` check - the former is syntactic sugar for the latter. – TheEvilPenguin Nov 27 '14 at 22:57
  • 1
    @TheEvilPenguin, OMG - he is checking for `null` just one line earlier... The difference is that my solution compiles and works. – Konrad Kokosa Nov 27 '14 at 22:58
  • Holy fast downvotes. This does work, even though I don't understand why it needs to be done this way. (Use `as`, instead of a cast when I have already done an `is`). – AndySavage Nov 27 '14 at 22:59
  • That explanation was edited in after my comment, and after other people posted answers with explanations. I don't think this conversation is productive, however, so I'm not going to repsond here any more. – TheEvilPenguin Nov 27 '14 at 23:04
1

Because the where restriction promises that TApiData is an IApiDataWithProperties; it does not guarantee about IApiDataWithProperties implemented types.

MORE:

Every Child would be an instance of Parent, but not vice versa. Think about this model:

interface I { void InterfaceNethod(); }

class A : I {
    void InterfaceMethod() { }
    void AMethod() { }
}

class B : I {
    void InterfaceMethod() { }
    void BMethod() { }
}

Now, lets get some instances, and call their methods:

I i = new A();
i.InterfaceMethod(); // it works
i.AMethod(); // it doesn't work, cause I has not a method named AMethod

And now, castings:

A a = new A();
a.InterfaceMethod(); // exists
a.AMethod(); // exists

I i = (I)a; // correct
i.InterfaceMethod(); // exists

B b = (B)i;
// if this be correct, then we should be able to call B's methods on b, right?
// While b hasn't any of B's members.
// I mean calling this:
b.BMethod();
// is logically incorrect. right? because, following the object's reference in memory
// would tell us that b is pointing to an A instance actually. Am I right?
// so the cast will fail. Because compiler knows about logic :) a little bit at least. cheers
amiry jd
  • 27,021
  • 30
  • 116
  • 215
0

You could declare the Log method inside the interface IApiDataWithProperties. Implement the Log method in your Event class and then where you are getting the exception cast the newItem to IApiDataWithProperties rather than Event.

((IApiDataWithProperties)newItem).Log

Or actually thinking about it you might not need a cast and can do

 newItem.Log
Azhar Khorasany
  • 2,712
  • 16
  • 20
-1

Your design is not the best to start with as it assumes an implementation of a specific class. But in case you want it done this way....

using as will work but you probably want it restricted also to reference types.

 where TApiData : IApiDataWithProperties, class

...

 (newItem as Event).Log();

Though this is not entirely safe as it can result in null you probably should check for that also

 var item = newItem as Event;
 if(item != null)
     item.Log();

You also can eliminate the line if(newItem is Event) as it is not required.

aqwert
  • 10,559
  • 2
  • 41
  • 61
  • This is no different from the cast originally used except it's slightly slower and exchanges a descriptive `TypeCastException` for a confusing `NullReferenceException`. It's bad practice to use `as` where a normal `(Type)` cast will do. – TheEvilPenguin Nov 27 '14 at 22:48
  • That's why you check for null. – aqwert Nov 27 '14 at 22:48
  • But that doesn't solve the problem, it just ignores it. The user wants the code to run, otherwise they'd catch and swallow the exception. – TheEvilPenguin Nov 27 '14 at 22:52
  • Actually it does solve the problem as the code compiles and runs without exception. If the OP wants to know it failed they can provide an else clause but that really depends on what he is trying to achieve. My answer is just addressing his issue of the cast not necessarily his entire design for which I only see a small part of. – aqwert Nov 27 '14 at 22:58
  • The goal of programming is not simply for the code to compile or finish without an exception, it's for the code to run correctly. It's obvious that the user wants the code to run if the object is an `Event`, but the correct-looking code they've written won't do that. – TheEvilPenguin Nov 27 '14 at 23:01
  • No solution other than having the interface have a Log method on it will be a air tight solution. – aqwert Nov 27 '14 at 23:03