Reputation: 941
I have something like:
public interface IThing
{
string Id { get; }
}
public class Ball : IThing
{
public string Id { get; }
}
public class Car : IThing
{
public string Id { get; }
}
For my 3-4 functions I want to treat Ball
and Car
the same. I use the interface so I dont have to make overload methods (one for car, one for ball).
Finally, there is on function where I have different logic if its a Ball
or a Car
. I get a IEnumerable<IThings>
and I want to cast it to either IEnumerable<Car>
or IEnumerable<Ball>
depending what it consists of. If it consists of a mix, I want it to fail. It has to be all cars or all balls.
I tried something like:
var things = (inputs is IEnumerable<Ball>) ? input.Locations.Cast<Ball>() : input.Locations.Cast<Car>()
But it doesn't like that. What is the recommended approach where I can have 1 variable?
Edit:
The reason why I wanted to get it into one variable is because I am sending it to an overloaded method. So I want to do this:
var things = (inputs is IEnumerable<Ball>) ? input.Locations.Cast<Ball>() : input.Locations.Cast<Car>()
for (var i = 0; i < numRequests; i++)
{
var thingsSet = things.Skip(i * 1000).Take(1000);
var results = callOverLoadedFunction(thingsSet);
}
Rather than this:
if (inputs is IEnumerable<Ball>)
{
var things = input.Locations.Cast<Ball>();
for (var i = 0; i < numRequests; i++)
{
var thingsSet = things.Skip(i * 1000).Take(1000);
var results = callOverLoadedFunction(thingsSet);
}
}
else
{
var things = input.Locations.Cast<Car>();
for (var i = 0; i < numRequests; i++)
{
var thingsSet = things.Skip(i * 1000).Take(1000);
var results = callOverLoadedFunction(thingsSet);
}
}
Upvotes: 1
Views: 2798
Reputation: 16112
Remember that IEnumerable<>
is covariant, that is, you can substitute IEnumerable<Derived>
whenever an IEnumerable<Base>
is needed.
If you have only pure containers of things, that is only things of the same kind are in a given container, then you should make that container (which will be passed as an IEnumerable at some point) of that specific type. For example a factory could produce a true list of cars, a List<car>
, in code like IEnumerable<Thing> things = factory.produceList(ThingTypes.Car);
. Like all types, IEnumerable<>
objects retain their actual type information even when they get assigned to references of a more basic type. This type can be used to distinguish the actual type of the IEnumerable<>
at run-time.
Perhaps some code is easier to understand. I create two IEnumerable<I>
s with elements of two distinct types which both implement the same interface I
. As I said, I can assign an IEnumerable<T>
to an IEnumerable<I>
as long as T
implements I
.
using System;
using System.Collections.Generic;
namespace ConsoleApplication34
{
interface I { };
class T1 : I { }
class T2 : I { }
class Program
{
// strongly typed arrays get assigned to base type IEnumerables.
static IEnumerable<I> i1 = new T1[] { new T1(), new T1() };
static IEnumerable<I> i2 = new T2[] { new T2(), new T2() };
static void Main(string[] args)
{
// Note: compile-time type of array elements is IEnumerable<I>!
IEnumerable<I>[] iEnumArr = new IEnumerable<I>[] { i1, i2 };
foreach (IEnumerable<I> ie in iEnumArr)
{
// ... but the run-time types of the IEnumerable objects
// are actually different.
Console.WriteLine("ienumerable is of T1: " + (ie is IEnumerable<T1>));
Console.WriteLine("ienumerable is of T2: " + (ie is IEnumerable<T2>));
}
}
}
}
The output is
ienumerable is of T1: True
ienumerable is of T2: False
ienumerable is of T1: False
ienumerable is of T2: True
Edit of the edit: I see that you are working with thingsSet
which is a true IEnumerable<Thing>
. True, then the type tests don't work any longer.
Edit: Your edit is a bit unclear to me, but I assume that your overloaded method has two (or n) versions, one for an IEnumerable<car>
and one for an IEnumerable<ball>
. In that case I would do everything which is independent on the concrete type of the Thing
first, and then distinguish only for the part where it matters. For example:
for (var i = 0; i < numRequests; i++)
{
var thingsSet = things.Skip(i * 1000).Take(1000);
// I may see your problem: Now with thingsSet we have true
// Enumerables of Thing, and the tests below are always false.
// Hm.
var carSet = thingsSet as IEnumerable<car>;
var ballSet = thingsSet as IEnumerable<ball>;
bool results;
if(carSet != null ) { results = callOverLoadedFunction(carSet); }
else if(ballSet != null) { results = callOverLoadedFunction(ballSet); }
else { throw /*...*/}
}
This solution has a bit of a code smell; ideally the calling code whouldn't be concerned with the concrete type of things. One possibility is to leave the "branching" for the distinct types to the Thing
s. Or if that isn't possible, provide a single callNonOverLoadedFunction(IEnumerable<Thing>)
with then branches internally, invisible to the caller. These functions are probably closer to the Things
implementation and "know" which different types of Things
exist; your calling code does not, and does not want to know, from a maintenance perspective.
Upvotes: 0
Reputation: 111940
I decided to redo something I did some time ago: the retransformation of partially a enumerated IEnumerator<>
to full IEnumerable<>
. This solves a problem that I feel is important: you shouldn't enumerate twice "unknown" IEnumerable<>
(for "unknown" I mean IEnumerable<>
that you haven't built by hand in the same method but that are of unknown origin), because there is no guarantee that it can be done, and even if it can be done, you could cause the big work needed to generate the IEnumerable<>
to be done twice.
public class RemainingIEnumerator<T> : IEnumerable<T>
{
public IEnumerable<T> Enumerable { get; set; }
public int Nulls { get; set; }
public T First { get; set; }
public IEnumerator<T> Enumerator { get; set; }
public IEnumerator<T> GetEnumerator()
{
var enumerator = Enumerator;
if (enumerator == null)
{
return Enumerable.GetEnumerator();
}
return GetEnumerableRemaining().GetEnumerator();
}
private IEnumerable<T> GetEnumerableRemaining()
{
var enumerator = Enumerator;
Enumerator = null;
int nulls = Nulls;
Nulls = 0;
T first = First;
First = default(T);
for (int i = 0; i < nulls; i++)
{
yield return default(T);
}
yield return first;
while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public static bool Is<T>(IEnumerable<T> enu, Type type, out IEnumerable<T> enu2)
{
IEnumerator<T> enumerator = null;
int nulls = 0;
try
{
enumerator = enu.GetEnumerator();
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (current == null)
{
nulls++;
continue;
}
enu2 = new RemainingIEnumerator<T>
{
Enumerable = enu,
Nulls = nulls,
First = current,
Enumerator = enumerator,
};
enumerator = null;
return current.GetType() == type;
}
// Only nulls case
enu2 = new T[nulls];
return false;
}
finally
{
if (enumerator != null)
{
enumerator.Dispose();
}
}
}
The Is<T>()
function returns true if the first non-null
element is of the type type
. It returns a new IEnumerable<>
that can be used and that, through "magic", reuses the IEnumerable<>
that was passed to Is<>
(in some way it restitches the optional initial null
s, the first found element and the unused remaining IEnumerator<>
).
Example of use:
var enu1 = new object[] { null, new Dog(), new Cat(), new Dog() };
IEnumerable<object> enu2;
// From this line onward, you should use at least one enu2!
// It is the partially unwinded enu1 that has been rewinded through
// some magic :-)
bool isDog = Is(enu1, typeof(Dog), out enu2);
if (isDog)
{
// Note the use of enu2!
foreach (Dog dog in enu2.Cast<Dog>())
{
}
}
Upvotes: 1
Reputation: 357
You can separate balls and cars from each other using LINQ
IEnumerable<Ball> balls = things.OfType<Ball>();
IEnumerable<Car> cars = things.OfType<Car>();
If you want it to fail and like one line solutions try something like this
IEnumerable<Ball> balls = things.OfType<Ball>().Count() == things.Count() ? things.OfType<Ball>() : null; //or whatever you want
Upvotes: 1
Reputation: 119186
The problem in your attempt it this:
inputs is IEnumerable<Ball>
Because an IEnumerable<IThing>
that only contains elements of type Ball
is not the same type as IEnumerable<Ball>
. You really have no choice but to enumerate through your collection to determine if every item matches the type you require. You could use .Cast<...>()
and handle the InvalidCastException
, but that's a little bit hacky. Another way would be to use OfType<...>
:
var cars = inputs.OfType<Car>();
var balls = inputs.OfType<Ball>();
And now you can deal with them as you wish, for example:
if(balls.Any() && cars.Any())
{
//You're not allowed to have balls and cars together
throw new Exception(...);
}
However, you are really breaking the open/closed principle of SOLID here, it seems like you should consider at a higher level what you are trying to achieve.
Upvotes: 2
Reputation: 2864
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<IThing> testCollection = new List<IThing>();
testCollection.Add(new Ball());
testCollection.Add(new Car());
try
{
if (testCollection[0] is Ball)
{
Console.WriteLine(testCollection.Cast<Ball>().Count().ToString());
}
else
{
Console.WriteLine(testCollection.Cast<Car>().Count().ToString());
}
}
catch(InvalidCastException ex)
{
Console.WriteLine("Mix isn't allowed!");
}
}
}
public interface IThing
{
string Id { get; set;}
}
public class Ball : IThing
{
public string Id { get;set; }
}
public class Car : IThing
{
public string Id { get;set; }
}
This code will throw an InvalidCastException at the call to Cast<Ball>
as the Car
object can't be cast to Ball
. This should do what you wanted, if I'm not mistaken.
The code will only check the type of the first element, as the List
shouldn't be mixed, it can be assumed that all the other objects in that List
should have the same type, if not that's, in my opinion and from how the question was written, appropiate cause to throw an exception.
Upvotes: 0
Reputation: 3253
You could do a convert method, but that would still break some principles, as you still have to place an if
statement.
I am not sure, you are using interfaces the right way with what you want to achieve.
If you want a car
to behave different from a ball
in a specific situation, then the implementation in the car shall do something different than the implementation in the ball.
Do not try to tweak an interface from outside. The implementations have to do this.
Why not create a method DoMySpecialStuff
in IThing
and you just iterate over your enumerable in this one special method that just calls DoMySpecialStuff
on all the elements?
That's the way you can avoid your if-statement.
I just saw your edit with your overloadedMethod
So it could work like this:
for (var i = 0; i < numRequests; i++)
{
var thingsSet = things.Skip(i * 1000).Take(1000);
var results = callOverLoadedFunction(thingsSet);
}
void OverLoadedFunction(IThing thing)
{
thing.DoSpecialStuff(); // This does different things in car/ball
}
Upvotes: 1