泛型类中的c#方法仅适用于某些类型

时间:2017-05-28 23:38:14

标签: c# generics

我正在尝试在泛型类上定义一个仅限于特定类型的方法。我想出了这个:

interface IHasId 
{
    int Id { get; }
}

public class Foo<T>
{
    private List<T> children;

    public IHasId GetById(int id)
    {
        foreach (var child in children.Cast<IHasId>())
        {
            if (child.Id == id)
            {
                return child;
            }
        }

        return null;
    }
}

它会工作,但它看起来像代码味道......似乎应该有一种方法让编译器强制执行此操作。类似的东西:

public class Foo<T>
{
    public IHasId GetById<TWithId>(int id) where TWithId : IHasId {}
}

或者,甚至更好:

public class Foo<T>
{
    public IHasId GetById(int id) where T : IHasId {}
}

我看到了一些与Java相关的帖子,其中一篇专门讨论将T限制为枚举,但没有直接关注点。

5 个答案:

答案 0 :(得分:3)

您不能拥有基于单一类型的可选方法。但是,您可以使用继承来使其工作。

以下是:

public interface IHasId 
{
    int Id { get; }
}

public class Foo<T>
{
    protected List<T> children;
}

public class FooHasId<T> : Foo<T> where T : IHasId
{
    public IHasId GetById(int id)
    {
        foreach (var child in children)
        {
            if (child.Id == id)
            {
                return child;
            }
        }
        return null;
    }
}

使用C#6 FooHasId可以缩短为:

public class FooHasId<T> : Foo<T> where T : IHasId
{
    public IHasId GetById(int id) => this.children.FirstOrDefault(x => x.Id == id);
}

答案 1 :(得分:1)

您可以向泛型添加多个约束,例如,此处GetById方法中的泛型必须为T和IHasId:

using System.Collections.Generic;
using System.Linq;

public interface IHasId
{
    public int Id { get; }
}

public class Foo<T>
{
    private List<T> children;

    public TItem GetById<TItem>(int id)
        where TItem : T, IHasId
    {
        return children.Where(t => t is TItem).Cast<TItem>().FirstOrDefault(t => t.Id == id);
    }
}

小提琴:https://dotnetfiddle.net/bD2Mpl

答案 2 :(得分:0)

您可以使用方法扩展来执行此操作,但是,您无法混合列表中的类型,因为方法扩展必须是显式的。

  • Method1(此Foo intFoo,int id)
  • Method2(此Foo stringFoo,字符串名称)

如果您使用类型作为通用类型,则扩展方法无法识别接口,因此您需要非常具体。

namespace Examples
{
    class Program
    {
        static void Main(string[] args)
        {
            Foo<IHasName> nameList = new Foo<IHasName>(new List<IHasName>()
            {
                new ObjectWithName("banana"),
                new ObjectWithName("apple")
            });

            Foo<IHasId> idList = new Foo<IHasId>(new List<IHasId>()
            {
                new ObjectWithId(1),
                new ObjectWithId(2),
                new ObjectWithId(3)
            });

            var obj1 = nameList.GetByName("banana");
            var obj2 = idList.GetById(2);
        }
    }

    public class ObjectWithName : IHasName
    {
        public string Name { get; }
        public ObjectWithName(string name)
        {
            Name = name;
        }
    }

    public class ObjectWithId : IHasId
    {
        public int Id { get; }
        public ObjectWithId(int id)
        {
            Id = id;
        }
    }

    public interface IHasName
    {
        string Name { get; }
    }

    public interface IHasId
    {
        int Id { get; }
    }

    public class Foo<T> : IEnumerable<T>
    {
        private IList<T> children;

        public Foo(IList<T> collection)
        {
            children = collection;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return children.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return children.GetEnumerator();
        }
    }

    public static class FooExtensions
    {
        public static IHasId GetById(this Foo<IHasId> foo, int id)
        {
            return foo.FirstOrDefault(c => c.Id == id);
        }

        public static IHasName GetByName(this Foo<IHasName> foo, string name)
        {
            return foo.FirstOrDefault(c => c.Name == name);
        }
    }
}

答案 3 :(得分:-1)

也许你可以使用2泛型,那么你可以在不同的级别应用约束?如果您尝试在违反约束的类型上调用GetById,我不确定它在编译或运行时是如何工作的。

public interface IHasId
{
    int Id { get; }
}

public class Foo<T>
{
    private List<T> children;

    public IHasId GetById<SubT>(int id)  where SubT : IHasId
    {
        foreach (var child in children.Cast<IHasId>())
        {
            if (child.Id == id)
            {
                return child;
            }
        }

        return null;
    }
}

答案 4 :(得分:-2)

您可以应用约束并像这样执行(使用一点linq,您的方法将如下所示):

public interface IHasId
{
    int Id { get; }
}

public class Foo<T> where T : IHasId
{
    private List<T> children;

    public IHasId GetById(int id)
    {
        return this.children.FirstOrDefault(x => x.Id == id);
    }
}

<强>更新

我将上面的答案保留为原因,因为这清楚地回答了OP的问题,而且任何低调的人都不理解泛型。

@Enigmativity在回答我的一条评论时发表了评论:

  

不,你没有回答这个问题。 OP希望方法受约束 - 而不是整个类。他希望T成为任何类型。

如果OP希望方法仅受约束而不是整个类,则答案如下:

仅限制方法

您不能仅对方法应用约束,因为在构造类时,必须关闭泛型。换句话说,您要在T字段中存储children(s)类型,类型T必须在类级别关闭。因此,类型T必须在类级别进行约束。

如果您希望该方法只是通用的,那么您不能在类级别使用类型T。您的类不必具有通用方法的通用性。您仍然可以在课程中使用通用方法,如下所示:

public class Foo
{
    // The children list is not of type T, but it has been closed with 
    // type SomeClass
    private List<SomeClass> children;

    // Here we are saying this method will work so long as it is called with
    // any T that implements IHasId
    // The class is not generic, but the method is
    public bool IsIdOver100<T>(T hasId) where T : IHasId
    {
        return hasId.Id > 100;
    } 
}

public class SomeClass : IHasId
{
    public int Id { get { /*...code*/ } }
} 

仅供参考,@ engimativity的答案也是在类级别应用约束,但指示使用继承。那仍将在类级别应用约束,继承没有区别。

@kblok在对这个答案的评论中也发表了类似的评论:

  

他询问如何在方法中应用约束,你没有回答这个问题

这就是解决方案,kblok建议:

public TItem GetById<TItem>(int id)
        where TItem : T, IHasId
{
    return children.Select(t => t is TItem).Cast<TItem>().FirstOrDefault(t => t.Id == id);
}

上面有两个问题:1。方法是强制转换,因此它不是通用的。 2.约束是TItem : T这是没有意义的,因为T不是封闭类型,并且T可以没有约束。我甚至不确定为什么编译器甚至会允许它,但可能有充分的理由。如果T在类级别(例如where T : IAnotherInterface)有约束,那么在方法级别放置一个约束是有意义的,该约束表示:where TItem : T, IHasId但仍然使方法保持非如果正在进行施法,那就是通用的。

OP的更多信息

如果你想继承Foo类,你可以在关闭或不关闭的情况下继续这样做。以下是关闭它的示例:

public class FooOfSomeClass : Foo<SomeClass>
{
    // you can have addition methods, properties stuff here like any other inheriting class
}