2

I have a piece of code that attempts to cast and return an object called m_obj depending on a given generic type. Here is the code:

private IObject m_obj;
public bool TryGetAs<T>(out T value) where T :IObject
{
    value = m_obj as T; // The type parameter 'T' cannot be used with the 'as' operator because it does not have a class type constraint nor a 'class' constraint

    // value = (T)m_obj; This compiles!

    if (value != null)
        return true;

    return false;
}

I know that if m_obj were a list, I could do this:

private readonly IList<Object> m_objects;

internal IList<T> Collect<T>() where T : IObject
{
    return m_objects.OfType<T>().ToList();
}

However, I cannot figure out how to solve the problem in the first code example. Here is the original code again:

public interface IObject
{
    
}

This is my proposed solution:

    public bool TryGetAs<T>(out T value) where T :IObject
    {
        value = default(T);
        if (!(m_obj is T))
            return false;

        value = (T)m_obj;
        return true;
    }

I would like to know if my solution is correct and if there are any better or more elegant ways to solve this problem.

Vahid
  • 5,144
  • 13
  • 70
  • 146
  • @mjwills Edited the question. – Vahid Nov 26 '17 at 11:22
  • Possible duplicate of [Using Generic Types with As operator](https://stackoverflow.com/questions/24253439/using-generic-types-with-as-operator) – mjwills Nov 26 '17 at 11:48
  • If you had `public struct Bob : IObject { }` and `m_obj` was `null`, what would you expect `TryGetAs` to set `value` to be? – mjwills Nov 26 '17 at 12:21

3 Answers3

4

The as keyword works with reference types.

The compiler doesn't know that T is indeed a reference type, so it shouts at you and won't compile.
You can tell the compiler that it's a reference type by adding the class constraint before the IObject constraint. Now you can safely cast.

Kaushik Thanki
  • 3,334
  • 3
  • 23
  • 50
Ofir Winegarten
  • 9,215
  • 2
  • 21
  • 27
  • Thanks Ofir, can you please help me understand why .OfType() seems to safely cast, although we are dealing with IObject here again! – Vahid Nov 26 '17 at 11:39
  • 1
    `OfType` uses `is` not `as`. https://github.com/Microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs – mjwills Nov 26 '17 at 11:42
  • @mjwills That is exactly what I mean! How in .OfType() 'is' works despite the fact that we do not know IObject is a class or struct but in the above code the compiler does not allow it. – Vahid Nov 26 '17 at 11:45
  • 1
    Because `is` doesn't require reference types. `as` needs it because of the null behaviour of it – Ofir Winegarten Nov 26 '17 at 11:47
  • :D you do not understand what I mean! I edited the question with the right answer. – Vahid Nov 26 '17 at 11:50
2

The as operator is reserved for reference types. If T is always a reference type, the solution is to add a class constraint to TryGetAs<T>. If T can also be a value type, this is not an option. In this case you can use the is operator:

public bool TryGetAs<T>(out T value) where T : IObject
{
    if(m_obj is T)
    {
        value = (T)m_obj;
        return true;
    }
    else
    {
        value = default(T);
        return false;
    }
}

In C# 7.0 you can simplify it like this (and improve performance since you don't need an is cast and then another type cast):

public bool TryGetAs<T>(out T value) where T : IObject
{
    if(m_obj is T tValue)
    {
        value = tValue;
        return true;
    }
    else
    {
        value = default(T);
        return false;
    }
}
Sefe
  • 13,731
  • 5
  • 42
  • 55
  • Thanks Sefe, this is exactly what I meant. I found it by the time you posted this I will mark it as an answer though, – Vahid Nov 26 '17 at 11:53
-3
public bool TryGetAs<T>(out T value) where T : class, IObject
{
    value = m_obj as T;

    return (value != null);
}

will act the same as your final solution, as well as likely being faster (since a null check is faster than an is check).

There is no benefit in not including the class modifier, if all of your IObject implementations are reference types (class) not value types (struct).

mjwills
  • 23,389
  • 6
  • 40
  • 63
  • The problem with you answer is that you do not consider that the T can be not a class. – Vahid Nov 26 '17 at 11:55
  • Even double struct in C# implements for example IComparable. You will be okay if you approach it with caution. – Vahid Nov 26 '17 at 12:13