将IEnumerable接口转换为具体类型

时间:2017-03-20 14:28:15

标签: c#

我有类似的东西:

    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。我使用界面,所以我不必制作重载方法(一个用于汽车,一个用于球)。

最后,如果它是BallCar,那么我的函数会有不同的逻辑。我得到一个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);
            }
        }

6 个答案:

答案 0 :(得分:1)

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

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

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

为什么不在DoMySpecialStuff中创建一个方法IThing,而只是在这个只在所有元素上调用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
}

答案 1 :(得分:1)

你尝试这个问题:

inputs is IEnumerable<Ball>

因为只包含IEnumerable<IThing>类型元素的Ball 不是IEnumerable<Ball>相同的类型。您真的别无选择,只能枚举您的收藏,以确定每个项目是否与您需要的类型相匹配。您可以使用.Cast<...>()并处理InvalidCastException,但这有点像hacky。另一种方法是使用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(...);
}

但是,你真的打破了这里的open/closed principle SOLID,你似乎应该考虑更高层次的目标。

答案 2 :(得分:1)

您可以使用LINQ

将球和车相互分开
IEnumerable<Ball> balls = things.OfType<Ball>().Count() == things.Count() ? things.OfType<Ball>() : null; //or whatever you want

如果你想让它失败并像一线解决方案那样试试

TX Begin
Select Balance
Logic in PHP
  Exception 
  Rollback
Commit

答案 3 :(得分:1)

我决定重做我前段时间做过的事情:将部分枚举的IEnumerator<>重新转换为完整的IEnumerable<>。这解决了一个我认为很重要的问题:你不应该两次枚举&#34;未知&#34; IEnumerable<>(对于&#34;未知&#34;我的意思是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();
        }
    }
}

如果第一个非Is<T>()元素的类型为null,则type函数返回true。它返回一个可以使用的新IEnumerable<>,通过&#34; magic&#34;,重复使用传递给IEnumerable<>的{​​{1}}(在某种程度上,它会重新选择可选的初始Is<> s,第一个找到的元素和未使用的剩余null)。

使用示例:

IEnumerator<>

答案 4 :(得分:0)

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中的所有其他对象应该具有相同的类型,如果不是那样,在我看来,问题是如何编写的,适当的原因就是抛出异常。

答案 5 :(得分:0)

请注意,IEnumerable<>是协变的,也就是说,只要需要IEnumerable<Derived>,您就可以替换IEnumerable<Base>

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

也许某些代码更容易理解。我用两个不同类型的元素创建了两个IEnumerable<I>,它们都实现了相同的接口I。正如我所说,只要IEnumerable<T>实施IEnumerable<I>,我就可以为T分配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>));
            }
        }
    }
}

输出

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

这个解决方案有一点代码味道;理想情况下,调用代码不关心具体的事物类型。一种可能性是离开&#34;分支&#34;对于Thing的不同类型。或者,如果不可能,请在内部提供单个callNonOverLoadedFunction(IEnumerable<Thing>),然后向调用者隐藏。这些函数可能更接近Things实现和&#34;知道&#34;存在哪种不同类型的Things;从维护角度来看,您的调用代码不会,也不想知道。