4

I have the following code

public class SortTerm<T> 
{

    public System.Func<T, System.IComparable> Sort;
    public SortDirection Direction;

    public SortTerm(System.Func<T, System.IComparable> sorter, SortDirection direction)
    {
        this.Sort = sorter;
        this.Direction = direction;
    }

    public SortTerm(System.Func<T, System.IComparable> sorter)
        : this(sorter, SortDirection.Ascending)
    { }


    public static SortTerm<T> Create<TKey>(System.Func<T, TKey> sorter, SortDirection direction)
        where TKey : System.IComparable
    {
        return new SortTerm<T>((System.Func<T, System.IComparable>)(object)sorter, direction);
    } // End Constructor 


    public static SortTerm<T> Create<TKey>(System.Func<T, TKey> sorter)
        where TKey : System.IComparable
    {
        return Create<TKey>(sorter, SortDirection.Ascending);
    } // End Constructor 

}

Which needs to cast a System.Func<T, TKey> to System.Func<T, IComparable>

Why does

SortTerm<Db.T_projects>.Create(x => x.name);

work, while

SortTerm<Db.T_projects>.Create(x => x.id);

gives an Invalid Cast

InvalidCastException: Unable to cast object of type 'System.Func2[Db.T_projects,System.Int64]' to type 'System.Func2[Db.T_projects,System.IComparable]'.

when long/Int64 is defined as

public struct Int64 : IComparable, IComparable<Int64>, IConvertible, IEquatable<Int64>, IFormattable

While string is defined no differntly as IComparable...

public sealed class String : IEnumerable<char>, IEnumerable, IComparable, IComparable<String>, IConvertible, IEquatable<String>, ICloneable

for completeness

public partial class T_projects
{
     public long id; // int not null
     public string name; // nvarchar(4000) not null
}

Shouldn't this work ?
And more importantly, how to make this work ?

Note:
There is gonna be a List<SortTerm<T>>, so I can't just use TKey in the sort-definition.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • 5
    What you try is a _co-variant_ conversion. This is not supported for _value-types_ (like `Int64`). `string` is a reference type, so it works. – René Vogt Feb 20 '18 at 09:22
  • 3
    https://stackoverflow.com/questions/12454794/why-covariance-and-contravariance-do-not-support-value-type (I don't close it as duplicate, as this does not answer the "more important" question of "how to make this work"). – René Vogt Feb 20 '18 at 09:24

2 Answers2

0

As René Vogt already told you in the comments, this is because co-variant conversion is not supported for value-types (like Int64), while it works for reference types (such as string).

Fix it by putting sorter inside a delegate with a matching signature.
A typecast-delegate, so to say:

public class SortTerm<T> 
{

    public System.Func<T, System.IComparable> Sort;
    public SortDirection Direction;

    public SortTerm(System.Func<T, System.IComparable> sorter, SortDirection direction)
    {
        this.Sort = sorter;
        this.Direction = direction;
    }

    public SortTerm(System.Func<T, System.IComparable> sorter)
        : this(sorter, SortDirection.Ascending)
    { }


    public static SortTerm<T> Create<TKey>(System.Func<T, TKey> sorter, SortDirection direction)
        where TKey : System.IComparable
    {
        // return new SortTerm<T>((System.Func<T, System.IComparable>)(object)sorter, direction);

        return new SortTerm<T>(delegate (T x) {
            TKey ret = sorter(x);
            return (System.IComparable)ret;
        }, direction);

    } // End Constructor 


    public static SortTerm<T> Create<TKey>(System.Func<T, TKey> sorter)
        where TKey : System.IComparable
    {
        return Create<TKey>(sorter, SortDirection.Ascending);
    } // End Constructor 

}

You might want to waive the where TKey : System.IComparable restriction, as this won't work for nullable-types, e.g.

SortTerm<Db.T_projects>.Create(x => x.created);

for

public partial class T_projects
{
     public long id; // int not null
     public string name; // nvarchar(4000) not null
     public System.DateTime? created; // datetime null
}

as AFAIK there is no way to

where TKey : System.IComparable or System.Nullable<System.IComparable>
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
0

As of the docs contra-variance only works for reference-types - e.g. string.

Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.

So you can do the following:

Func<string> myFunc = ...
Func<object> func = myFunc;

but not this:

Func<int> myFunc = ...
Func<object> func = myFunc;

To circumvent this, either box your id to object:

Func<object> = () => (object) id;

which would be the following in your code:

SortTerm<Db.T_projects>.Create(x => (object) x.id);

or create a new delegate upon the existing one:

Func<object> = () => myFunc();

which corresponds to this in your code:

SortTerm<Db.T_projects>.Create(new Func<MyType, object>(x => x.id));
MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111