将 IE可枚举接口转换为具体类型



我有这样的东西:

public interface IThing
{
string Id { get; }
}
public class Ball : IThing
{
public string Id { get; }
}
public class Car : IThing
{
public string Id { get; }
}

对于我的 3-4 个功能,我想对待BallCar相同。我使用界面,所以我不必制作过载方法(一个用于汽车,一个用于球)。

最后,在函数中,如果它是Ball还是Car,我有不同的逻辑。我得到了一个IEnumerable<IThings>,我想根据它的组成将其转换为IEnumerable<Car>IEnumerable<Ball>。如果它由混合组成,我希望它失败。它必须是所有的汽车或所有的球。

我尝试了类似的东西:

var things = (inputs is IEnumerable<Ball>) ? input.Locations.Cast<Ball>() : input.Locations.Cast<Car>()

但它不喜欢这样。我可以有 1 个变量的推荐方法是什么?

编辑:

我想把它放到一个变量中的原因是因为我把它发送到一个重载的方法。所以我想这样做:

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);
}

而不是这个:

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);
}
}

您尝试的问题如下:

inputs is IEnumerable<Ball>

因为仅包含类型为Ball的元素的IEnumerable<IThing>IEnumerable<Ball>的类型不同。您真的别无选择,只能枚举您的集合,以确定每个项目是否与您需要的类型匹配。您可以使用.Cast<...>()并处理InvalidCastException,但这有点笨拙。另一种方法是使用OfType<...>

var cars = inputs.OfType<Car>();
var balls = inputs.OfType<Ball>();

现在您可以根据需要处理它们,例如:

if(balls.Any() && cars.Any())
{
//You're not allowed to have balls and cars together
throw new Exception(...);
}

但是,您在这里确实打破了SOLID的开放/封闭原则,似乎您应该在更高的层次上考虑您要实现的目标。

你可以做一个转换方法,但这仍然会破坏一些原则,因为你仍然需要放置一个if语句。 我不确定您是否以正确的方式使用接口来实现您想要实现的目标。

如果您希望car在特定情况下的行为与ball不同,那么汽车中的实现应该与球中的实现不同。

不要尝试从外部调整界面。实现必须这样做。

为什么不创建一个DoMySpecialStuffIThing的方法,然后你只是在这个特殊方法中迭代你的枚举,只对所有元素调用DoMySpecialStuff? 这是你可以避免你的if语句的方法。

我刚刚用你的overloadedMethod看到了你的编辑

所以它可以像这样工作:

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
}

您可以使用 LINQ 将球和汽车彼此分开

IEnumerable<Ball> balls = things.OfType<Ball>();
IEnumerable<Car> cars = things.OfType<Car>();

如果您希望它失败并像单行解决方案一样尝试这样的事情

IEnumerable<Ball> balls = things.OfType<Ball>().Count() == things.Count() ? things.OfType<Ball>() : null; //or whatever you want

我决定重做我前段时间做过的事情:将部分枚举IEnumerator<>重新转换为完全IEnumerable<>。这解决了一个我认为很重要的问题:你不应该枚举两次"未知"IEnumerable<>(对于"未知",我的意思是IEnumerable<>你没有用相同的方法手动构建,而是来源不明),因为不能保证它可以完成,即使可以完成,你也可能导致生成IEnumerable<>所需的大工作完成两次。

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();
}
}
}

如果第一个非null元素的类型为type,则Is<T>()函数返回 true。它返回一个可以使用的新IEnumerable<>,并且通过"magic"重用传递给Is<>IEnumerable<>(以某种方式,它会重新缝合可选的初始nulls,第一个找到的元素和未使用的剩余IEnumerator<>)。

使用示例:

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>())
{
}
}
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; }
}

此代码将在调用Cast<Ball>时引发 InvalidCastException,因为Car对象不能强制转换为Ball。如果我没记错的话,这应该做你想做的事。

代码只会检查第一个元素的类型,因为List不应该混合,可以假设该List中的所有其他对象都应该具有相同的类型,如果不是,在我看来,从问题的编写方式来看,这是引发异常的适当原因。

请记住,IEnumerable<>是协变的,也就是说,您可以在需要IEnumerable<Base>时替换IEnumerable<Derived>

如果你只有纯粹的容器,也就是说,只有相同种类的东西在给定的容器中,那么你应该使该容器(在某个时候将作为 IEnumerable 传递)为该特定类型。例如,一家工厂可以用像IEnumerable<Thing> things = factory.produceList(ThingTypes.Car);这样的代码生成一个真正的汽车列表,一个List<car>。与所有类型一样,IEnumerable<>对象保留其实际类型信息,即使它们被分配给更基本类型的引用也是如此。此类型可用于在运行时区分IEnumerable<>的实际类型。

也许有些代码更容易理解。我创建了两个IEnumerable<I>,其中包含两种不同类型的元素,它们都实现了相同的接口I。正如我所说,只要T实现I,我就可以为IEnumerable<I>分配IEnumerable<T>

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>));
}
}
}
}

输出为

ienumerable is of T1: True
ienumerable is of T2: False
ienumerable is of T1: False
ienumerable is of T2: True

编辑编辑:我看到您正在使用thingsSet这是一个真正的IEnumerable<Thing>。没错,那么类型测试不再起作用。

编辑:您的编辑对我来说有点不清楚,但我假设您的重载方法有两个(或 n)个版本,一个用于IEnumerable<car>,一个用于IEnumerable<ball>。在这种情况下,我会先做所有独立于Thing具体类型的事情,然后只区分它重要的部分。例如:

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 /*...*/}
}

这个解决方案有点代码气味;理想情况下,调用代码不关心具体类型的东西。一种可能性是将不同类型的"分支"留给Thing。或者,如果无法做到这一点,请在内部提供单个callNonOverLoadedFunction(IEnumerable<Thing>)分支,调用方不可见。这些功能可能更接近Things实现,并"知道"存在哪些不同类型的Things;从维护的角度来看,您的调用代码不会也不想知道。

最新更新