1

Supposing I create an extension method for IList but this extension is part of a library potentially used across many projects. I do not have the control on how it is called.

Is there a way to prevent an Array to call an IList<T> extension method at compile time? This to avoid any misuse, the caller cannot guess the exact implementation, if the .Add() method would be called or only the indexer for example. I could not find a possible solution with generic constraint type.

So far the only possibility left would be to restrict the extension method to List<T> directly.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        var array = new[]{"Hello"};
        array.DummyInsert("World"); // this will crash at run time
    }
}

public static class DummyExtension
{
    public static T DummyInsert<T>(this IList<T> list, T insertValue)
    {
        list.Add(insertValue);
        return insertValue;
    }
}
LeBaptiste
  • 1,156
  • 14
  • 20
  • 2
    Let me see if I understand your question: you want to be able to create an array, but you also want compilation to fail if, anywhere in your code, a method defined on IList is used on that array? If that's so, then I'd suggest creating your own array class that doesn't implement IList. Then the only methods available to be called on that array would be those that you've explicitly defined as part of your class. – TheRotag May 16 '17 at 18:43
  • What do you want it to do if it does call into the extension method? – thinklarge May 16 '17 at 18:46
  • Use `ReadOnlyCollection`. You don't want an array. You want a read-only collection. – 15ee8f99-57ff-4f92-890c-b56153 May 16 '17 at 18:47
  • I made an edit, the question is more about give a restriction of the use of the extension more than passing another type directly. – LeBaptiste May 16 '17 at 18:58
  • @LeBaptiste, Do you want this to throw a compile time exception? – thinklarge May 16 '17 at 19:10
  • 2
    Your problem statement is so vague, there are many different possible answers. Fact is, if you declare your extension method with `IList`, then any `IList` can be passed to it. Period. If the method would do something that would require changing the length of the collection, then you'll just have to know to not pass an array to it. Which, frankly, should be obvious from your implementation; if not, you should work on the design of your method (e.g. naming, documentation, etc.) – Peter Duniho May 16 '17 at 19:11
  • 1
    I believe what he wants is the ability to restrict the IList extension method to exclude Arrays. – NetMage May 16 '17 at 19:37

3 Answers3

3

You can add your extension method to List<T> not on IList<T>

Ilya Chernomordik
  • 27,817
  • 27
  • 121
  • 207
  • @PeterDuniho I upvoted it. "Sock puppet". Good heavens. – 15ee8f99-57ff-4f92-890c-b56153 May 16 '17 at 19:20
  • @Ed: really? I find it bizarre you'd do that. Why not also up-vote the obviously superior answer? But thanks for the clarification. I obviously jumped to an unwarranted conclusion. – Peter Duniho May 16 '17 at 19:22
  • @PeterDuniho It's hardly the most bizarre thing I've seen in this discussion. – 15ee8f99-57ff-4f92-890c-b56153 May 16 '17 at 19:25
  • 2
    @EdPlunkett Hey, my answer is bizarre, but it works :-) Also, this answer does restate something that was in the OPs question so I was confused too by the upvote. I mean my answer is a direction you wouldn't want someone to take but I actually answer the question that's asked. – thinklarge May 16 '17 at 19:27
  • @Ed: never said it was the _most_ bizarre thing. We have room for shades of grey here. :) – Peter Duniho May 16 '17 at 19:28
  • 2
    I accepted this answer as it seems to be the right compromise to have. – LeBaptiste May 16 '17 at 19:34
  • 1
    @thinklarge I think it's perfectly reasonable to use an upvote to say "I think this is a good solution to the question". If the answer is essentially saying "you already know the answer", that's OK. OP didn't suggest that extending `List` was out of the question. It's much better to be right than original. We're not writing poetry here. – 15ee8f99-57ff-4f92-890c-b56153 May 16 '17 at 19:34
  • 2
    @LeBaptiste I think you're right. You want to extend the most general thing possible, but no generaller. `IList` is just a little too general; you're sweeping something unwanted into the net. I'm surprised .NET has array implementing it, since arrays don't support everything it does. – 15ee8f99-57ff-4f92-890c-b56153 May 16 '17 at 19:34
  • 1
    @EdPlunkett, there is a lengthy question concerning partial implementation: http://stackoverflow.com/questions/11163297/how-do-arrays-in-c-sharp-partially-implement-ilistt – LeBaptiste May 16 '17 at 19:42
  • @LeBaptiste Thanks! One of my favorite heuristics is "whenever you think you're smarter than the .NET team, you're wrong." – 15ee8f99-57ff-4f92-890c-b56153 May 16 '17 at 19:43
2

I Agree with Ed Plunkett, use a ReadOnlyCollection<T>. But you can do it like this. It's your foot, you can shoot it if you want.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        var array = new[]{"Hello"};
        var world = array.Insert("World"); // this will crash at run time
        
        Console.WriteLine(array.Length);
    }
}

public static class DummyExtension
{
    public static T Insert<T>(this IList<T> list, T insertValue)
    {
        Console.WriteLine("WrongInsert");
        list.Add(insertValue);
        return insertValue;
    }
    
    [Obsolete("If want a compile time exception you can do this too.", true)]
    public static T Insert<T>(this T[] list, T insertValue)
    {
        Console.WriteLine("RightInsert");
        return insertValue;
    }
}

This prints

RightInsert

1

https://dotnetfiddle.net/i6p1Z5

EDIT:

It was pointed out in the comments below that this won't work if your array has been cast to an IList<T> either explicitly or implicitly. There is nothing wrong with using List<T> here instead of IList<T> unless you are trying to actually extend the IList<T>. In that case extend it in a way that makes sense for all IList<T>. I just wanted to show that yes, what you ask can be done. With great power comes great responsibility.

Community
  • 1
  • 1
thinklarge
  • 672
  • 8
  • 24
  • 2
    I'd have stopped after the first sentence. Overloading `Insert` for all arrays is madness. He wouldn't just be shooting himself in the foot; he'd be shooting all feet. Actually that would be a good addition to the ["shoot yourself in the foot" joke](http://www.toodarkpark.org/computers/humor/shoot-self-in-foot.html). "C# programmers use extension methods to shoot every foot in existence simultaneously". – 15ee8f99-57ff-4f92-890c-b56153 May 16 '17 at 18:58
  • 1
    @EdPlunkett I'm going to get meta on you. Should I have answered it? He asked a question and I answered in a way that solves the problem with a disclaimer. I'm also really proud of the answer, I'd never do it, but it's possible and works. Should I have actually not answered the question and left this as a dead question, or should I show him something he might find on his own and say, don't use it so that he knows that it's a bad practice? – thinklarge May 16 '17 at 19:02
  • 1
    My rule of thumb is -1 could be somebody having a bad day, but below that it's time to pull the plug. At 0, there's no net consensus that it shouldn't be here. Keep it. – 15ee8f99-57ff-4f92-890c-b56153 May 16 '17 at 19:04
  • 1
    @EdPlunkett Yes you are right, by simplifying my question to a very generic case I picked the wrong wording. "Insert()" was for the sake of giving a name to the extension! – LeBaptiste May 16 '17 at 19:06
  • @LeBaptiste That said, I actually misunderstood your question at first. I'd advise just letting the exceptions get thrown, or maybe doing a rethrow in your extension method with a more informative message. You can't fix this at compile time. – 15ee8f99-57ff-4f92-890c-b56153 May 16 '17 at 19:11
  • 1
    @EdPlunkett, about shooting yourself in the foot, I think you would be safe however since extensions methods are not called if a proper method already exists, extensions method have a lower priority when resolving the correct implementation to call, but I would not try it :) – LeBaptiste May 16 '17 at 19:13
  • @EdPlunkett OK sorry this cracks me up but I got it to throw a compile time exception above. LOL – thinklarge May 16 '17 at 19:13
  • It's worth pointing out that this approach works only if the array is not already cast to `IList` at the call site, either with an explicit cast (something a programmer not paying attention to the semantics of the method in the first place might actually do), or by virtue of having been passed or otherwise assigned to a variable of type `IList`. It's a reasonably clever work-around to a poorly-stated problem, but doesn't really fix the underlying issue. – Peter Duniho May 16 '17 at 19:26
  • 1
    @thinklarge Incidentally, "bizarre" is not a word I'd have used to describe your answer. – 15ee8f99-57ff-4f92-890c-b56153 May 16 '17 at 21:44
0

The run-time issue is because of the fact that the Array is of fixed length hence when you try to insert an element into it you end up with an exception. Instead you can have your own extension method for case Array and handle the insertion accordingly.

public class Program
{
    public static void Main()
    {
        var array = new[] { "Hello" };
        array = array.Insert("World");
    }
}

public static class DummyExtension
{
    public static T Insert<T>(this IList<T> list, T insertValue)
    {
        list.Add(insertValue);
        return insertValue;
    }

    public static T[] Insert<T>(this T[] list, T insertValue)
    {

        var destArray = new T[list.Length + 1];
        Array.Copy(list, destArray, list.Length);
        destArray[destArray.Length - 1] = insertValue;

        return destArray;
    }
}

Well I agree it may be a crude way, but it will work for your case.

Gururaj
  • 539
  • 2
  • 8