9

I'm trying to create an implicit cast that will allow me to use a LINQ result to directly return MyCollection.

public class MyCollection : ICollection<MyType> 
{
    private List<MyType> _list = new List<MyType>();

    public MyCollection(IEnumerable<MyType> collection) 
    {
        _list = new List<MyType>(collection);
    }

    public static implicit operator MyCollection(IEnumerable<MyType> collection) 
    {
        return new MyCollection(collection);
    }

    // collection methods excluded for brevity

    public MyCollection Filter(string filter) 
    {
        return _list.Where(obj => obj.Filter.Equals(filter)); // cannot implicitly convert
    }
}

I've not tried using implicit user-defined casting before, what am I doing wrong?

Xaruth
  • 4,034
  • 3
  • 19
  • 26
Alex
  • 97
  • 1
  • 5
  • 2
    possible duplicate of [C# compiler bug? Why doesn't this implicit user-defined conversion compile?](http://stackoverflow.com/questions/1208796/c-sharp-compiler-bug-why-doesnt-this-implicit-user-defined-conversion-compile) – decPL Mar 14 '14 at 10:33
  • Surely this would only be a duplicate if `MyType` was itself an interface? I'm merely implementing an interface on `MyCollection` – Alex Mar 14 '14 at 10:46
  • 1
    You aren't casting from `MyType`, you're casting from `IEnumerable`, and `IEnumerable` is an interface. – Jon Hanna Mar 14 '14 at 10:57
  • Ah yes I see now thanks to you answer – Alex Mar 14 '14 at 11:16
  • 1
    despite marking this as a duplicate, it must be said that Jon's answer below is much more detailed (and hopefully helpful) than the one in the original question – decPL Mar 14 '14 at 11:22

5 Answers5

8

You aren't allowed to use implicit when either the type cast from, or the type cast to is an interface type. (You also aren't allowed them if one type is derived from the other, which as such bars object as ever being allowed). Indeed, you aren't allowed explicit in this case either. From section §17.9.3 of ECMA-364:

A class or struct is permitted to declare a conversion from a source type S to a target type T only if all of the following are true, where S0 and T0 are the types that result from removing the trailing ? modifiers, if any, from S and T:

  • S0 and T0 are different types.

  • Either S0 or T0 is the class or struct type in which the operator declaration takes place.

  • Neither S0 nor T0 is an interface-type.

  • Excluding user-defined conversions, a conversion does not exist from S to T or from T to S.

You are breaking both the third rule (interface type) and the fourth (because there's a non-user-defined conversion from MyCollection to IEnumerable<MyType> already).

If it were allowed, I'd recommend against it anyway.

Implicit casts should only be used when the effect is utterly obvious (to someone with a reasonable knowledge of the language): It's utterly obvious what long x = 3 + 5 does in casting int to long, and utterly obvious what object x = "abc" does in casting string to object.

Unless your use of implicit is of a similar level of "obvious", then it is a bad idea.

In particular, generally implicit casts should not be implicit in the opposite direction, but rather they should be implicit in one direction (the "widening" direction in most built-in cases) and explicit in the opposite direction (the "narrowing" direction). Since you've already got an implicit cast available from MyCollection to IEnumerable<MyCollection>, having an implicit cast available in the opposite direction is pretty much a bad idea.

More generally, since you are talking about use of Linq, there's an even stronger benefit in using an extensible ToMyCollection() method, because then you are going to be following the Linq convention of ToArray(), ToList(), etc.:

public static class MyCollectionExtensions
{
  public static MyCollection ToMyCollection(this IEnumerable<MyType> collection) 
  {
      return collection as MyCollection ?? new MyCollection(collection);
  }
}

Note that I test for the case where the collection is already MyCollection to avoid wasteful repeated constructions. You may or may not also want to handle the case of List<MyType> specially in using an internal constructor that assigned it to _list directly.

However, you need to consider the aliasing effects that would allow before doing so. This can be a very useful trick if you know aliasing can't cause problems (either the class is only used internally and the aliasing known not to be an issue, or aliasing doesn't hurt the use of MyCollection, or the aliasing is actually desirable). If in doubt, then just have the method do return new MyCollection(collection) to be safer.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • "Note that I test for the case where the collection is already MyCollection to avoid wasteful repeated constructions". This then adds some surprising behavior, because ToXXX methods are usually expected to create new instance. Same with the next sentence. That would create completely unexpected indirect link. – Euphoric Mar 14 '14 at 10:54
  • @Euphoric I did say that you need to consider aliasing effects, but I'll edit to be clearer on that. – Jon Hanna Mar 14 '14 at 10:59
  • 1
    @JonHanna you might have missed `this` in your "extension" method – decPL Mar 14 '14 at 11:19
6

You could use custom extension method instead of implicit conversion:

public static class Extension
{
    public static MyCollection ToMyCollection(this IEnumerable<MyType> enumerable)
    {
        return new MyCollection(enumerable);
    }
}

And then use it same as ToList :

return _list.Where(obj => obj.Filter.Equals(filter)).ToMyCollection();
Euphoric
  • 12,645
  • 1
  • 30
  • 44
  • 1
    This is nice because it's similar to the `ToList` and `ToArray` extension methods that already exist. – brimble2010 Mar 14 '14 at 10:35
  • What if consuming code were to use LINQ extension methods with my collection. Do I have to re-implement all of them so that they return `MyCollection` – Alex Mar 14 '14 at 11:04
  • 1
    No, because `MyCollection` already implements `IEnumerable`, so all the Linq extension methods will work with it already. – Jon Hanna Mar 14 '14 at 11:13
  • Let me re-phase, do I have to re-implement the LINQ extensions which would otherwise return `IEnumerable` so that they return `MyCollection` as intended? – Alex Mar 14 '14 at 11:21
  • If you wanted them to do so, then yes. Why would you want them to? Generally, as with `ToList`, one would just have `ToMyCollection` as the very last thing done at the point where one wants a `MyCollection`. Otherwise you'll have new `MyCollection` objects being created at each stage, which would be a waste of time and memory if you were then going to do another Linq operation on them. – Jon Hanna Mar 14 '14 at 11:30
  • Well I implement `ToString()` so that `MyCollection` can be used in an MVC view. Code in the view would be a lot nicer to read if it were `Model.MyList.Where(x => x.ID == 3)` rather than `new MyCollection(Model.MyList.Where(x => x.ID == 3))` – Alex Mar 14 '14 at 11:34
  • 1
    That's why you would use `Model.MyList.Where(x => x.ID == 3).ToMyCollection()`. Consider, `Model.MyList.Where(x => x.ID == 3).OrderBy(x => x.ID).Take(5)`. If `Where` and `OrderBy` returned a `MyCollection` then they would waste time and memory building a list inside a `MyCollection`, just to have it used once in the next method and then collected. Re-implementing `Linq` only makes sense if you are either going to do deferred execution (which `MyCollection` doesn't) or perhaps if you are changing a view on immutably held collections (again, not so here). – Jon Hanna Mar 14 '14 at 12:03
1

This is the actual line which won't work:

public static implicit operator MyCollection(IEnumerable<MyType> collection)

because what you are trying to do is not allowed:

error CS0552: user-defined conversions to or from an interface are not allowed

You can break too much of CLR internals if redefine interface to class casts, so you are not allowed to.

Best way to proceed is to use custom extension method like @Euphoric advices.

Lanorkin
  • 7,310
  • 2
  • 42
  • 60
0

In case you want to extend some functionality of IEnumerable, you can easily use an extenstion:

public static class Extensions
{
    public IEnumerable<MyType> Filter(this IEnumerable<MyType> e, string filter)
    {
        return e.Where(T => T.Filter.Equals(filter));
    }
}

That will make it work even without casting operators.


In case you want your implicit casting, this tells you that's not that easy.

Community
  • 1
  • 1
AgentFire
  • 8,944
  • 8
  • 43
  • 90
0

Why do you need an ICollection? I would use IEnumerable as in the code below. If you need an ICollection instead, just replace IEnumerable below with ICollection, but be consistent and use one or the other, not a mix of both.

public class MyCollection : IEnumerable<MyType>
{
    private IEnumerable<MyType> _list;

    public MyCollection(IEnumerable<MyType> collection)
    {
        _list = collection;
    }

    // collection methods excluded for brevity

    public MyCollection Filter(string filter)
    {
        return new MyCollection(_list.Where(obj => obj.Filter.Equals(filter))); 
    }

Please note: if you use ICollection instead of IEnumerable, the Filter command should be:

return new MyCollection(_list.Where(obj => obj.Filter.Equals(filter)).ToList());

AndreCruz
  • 653
  • 4
  • 12
  • Because it is a collection. Not just an enumerable list. IEnumerable does not include Add, Remove etc. – Alex Mar 14 '14 at 10:49