25

.Net 3.5 中的新扩展允许功能从接口中分离出来。

例如在 .Net 2.0

public interface IHaveChildren {
    string ParentType { get; }
    int ParentId { get; }

    List<IChild> GetChildren()
}

可以(在 3.5 中)变为:

public interface IHaveChildren {
    string ParentType { get; }
    int ParentId { get; }
}

public static class HaveChildrenExtension {
    public static List<IChild> GetChildren( this IHaveChildren ) {
        //logic to get children by parent type and id
        //shared for all classes implementing IHaveChildren 
    }
}

在我看来,这对于许多接口来说是一种更好的机制。他们不再需要一个抽象的基础来共享此代码,并且在功能上代码的工作方式相同。这可以使代码更易于维护和测试。

唯一的缺点是抽象基实现可以是虚拟的,但可以解决这个问题(实例方法会隐藏同名的扩展方法吗?这样做会混淆代码吗?)

还有其他不经常使用这种模式的理由吗?


澄清:

是的,我看到扩展方法的趋势是到处都有它们。在没有大量同行评审的情况下,我会特别小心地使用任何.Net 值类型(我认为我们在字符串上唯一的一个是.SplitToDictionary()- 类似于.Split()但也采用键值分隔符)

我认为那里有一个完整的最佳实践辩论;-)

(顺便说一句:DannySmurf,你的 PM 听起来很吓人。)

我在这里特别询问在以前我们有接口方法的地方使用扩展方法。


我试图避免很多层次的抽象基类——实现这些模型的类大多已经有了基类。我认为与添加更多对象层次结构相比,该模型更易于维护且耦合更少。

这是 MS 对 Linq 的 IEnumerable 和 IQueryable 所做的吗?

4

11 回答 11

10

扩展方法应该只是这样使用:扩展。任何与结构/设计相关的关键代码或重要操作都应放在由类或接口组成/继承自的对象中。

一旦另一个对象尝试使用扩展对象,他们将看不到扩展,并且可能不得不再次重新实现/重新引用它们。

传统观点是扩展方法应该只用于:

  • 实用程序类,正如 Vaibhav 提到的
  • 扩展密封的第 3 方 API
于 2008-08-12T07:23:55.130 回答
9

我认为扩展方法的明智使用使接口与(抽象)基类处于更平等的位置。


版本控制。基类相对于接口的一个优势是您可以在以后的版本中轻松添加新的虚拟成员,而向接口添加成员会破坏针对旧版本库构建的实现者。相反,需要创建具有新成员的新版本接口,并且库将不得不解决或限制对仅实现原始接口的遗留对象的访问。

作为一个具体的例子,一个库的第一个版本可能会定义一个这样的接口:

public interface INode {
  INode Root { get; }
  List<INode> GetChildren( );
}

库发布后,我们无法在不破坏当前用户的情况下修改界面。相反,在下一个版本中,我们需要定义一个新接口来添加额外的功能:

public interface IChildNode : INode {
  INode Parent { get; }
}

但是,只有新库的用户才能实现新接口。为了使用遗留代码,我们需要调整旧的实现,扩展方法可以很好地处理:

public static class NodeExtensions {
  public INode GetParent( this INode node ) {
    // If the node implements the new interface, call it directly.
    var childNode = node as IChildNode;
    if( !object.ReferenceEquals( childNode, null ) )
      return childNode.Parent;

    // Otherwise, fall back on a default implementation.
    return FindParent( node, node.Root );
  }
}

现在,新库的所有用户都可以一视同仁地对待传统实现和现代实现。


过载。扩展方法可能有用的另一个领域是为接口方法提供重载。您可能有一个带有多个参数的方法来控制其操作,其中只有前一两个参数在 90% 的情况下很重要。由于 C# 不允许为参数设置默认值,因此用户要么每次都必须调用完全参数化的方法,要么每个实现都必须为核心方法实现琐碎的重载。

相反,扩展方法可用于提供琐碎的重载实现:

public interface ILongMethod {
  public bool LongMethod( string s, double d, int i, object o, ... );
}

...
public static LongMethodExtensions {
  public bool LongMethod( this ILongMethod lm, string s, double d ) {
    lm.LongMethod( s, d, 0, null );
  }
  ...
}


请注意,这两种情况都是根据接口提供的操作编写的,并且涉及琐碎或众所周知的默认实现。也就是说,您只能从一个类继承一次,并且有针对性地使用扩展方法可以提供一种有价值的方法来处理接口缺乏的基类提供的一些细节:)


编辑: Joe Duffy 的相关帖子:扩展方法作为默认接口方法实现

于 2008-09-01T21:52:48.700 回答
6

我认为扩展方法取代的最好的东西是您在每个项目中找到的所有实用程序类。

至少就目前而言,我觉得任何其他扩展方法的使用都会在工作场所引起混乱。

我的两个位。

于 2008-08-11T18:56:39.117 回答
2

扩展接口并没有错,事实上 LINQ 就是这样将扩展方法添加到集合类中的。

话虽这么说,你真的应该只在需要为实现该接口的所有类提供相同功能并且该功能不是(并且可能不应该是)任何派生的“官方”实现的一部分的情况下这样做类。如果为需要新功能的每个可能的派生类型编写扩展方法是不切实际的,那么扩展接口也很好。

于 2008-08-17T04:40:58.843 回答
2

我认为使用扩展方法分离域/模型和 UI/视图功能是一件好事,特别是因为它们可以驻留在单独的命名空间中。

例如:

namespace Model
{
    class Person
    {
        public string Title { get; set; }
        public string FirstName { get; set; }
        public string Surname { get; set; }
    }
}

namespace View
{
    static class PersonExtensions
    {
        public static string FullName(this Model.Person p)
        {
            return p.Title + " " + p.FirstName + " " + p.Surname;
        }

        public static string FormalName(this Model.Person p)
        {
            return p.Title + " " + p.FirstName[0] + ". " + p.Surname;
        }
    }
}

这种方式扩展方法可以与 XAML 数据模板类似地使用。您不能访问该类的私有/受保护成员,但它允许维护数据抽象,而无需在整个应用程序中进行过多的代码重复。

于 2008-08-17T09:00:27.943 回答
2

多一点点。

如果多个接口具有相同的扩展方法签名,则需要将调用者显式转换为一种接口类型,然后调用该方法。例如

((IFirst)this).AmbigousMethod()
于 2010-01-08T04:06:05.643 回答
1

哎哟。请不要扩展接口。
接口是一个类应该实现的干净契约,您对所述类的使用必须限制在核心接口中的内容才能正常工作。

这就是为什么你总是将接口声明为类型而不是实际的类。

IInterface variable = new ImplementingClass();

对?

如果你真的需要一些附加功能的契约,抽象类是你的朋友。

于 2008-08-12T07:38:23.650 回答
1

我看到很多人提倡使用基类来共享通用功能。小心这一点 - 你应该更喜欢组合而不是继承。继承应该只用于多态性,从建模的角度来看是有意义的。它不是代码重用的好工具。

至于问题:当心这样做的限制 - 例如在显示的代码中,使用扩展方法来实现 GetChildren 有效地“密封”了这个实现,并且不允许任何 IHaveChildren impl 在需要时提供它自己的。如果这没问题,那么我不介意扩展方法方法。它不是一成不变的,通常可以在以后需要更多灵活性时轻松重构。

为了获得更大的灵活性,使用策略模式可能更可取。就像是:

public interface IHaveChildren 
{
    string ParentType { get; }
    int ParentId { get; }
}

public interface IChildIterator
{
    IEnumerable<IChild> GetChildren();
}

public void DefaultChildIterator : IChildIterator
{
    private readonly IHaveChildren _parent;

    public DefaultChildIterator(IHaveChildren parent)
    {
        _parent = parent; 
    }

    public IEnumerable<IChild> GetChildren() 
    { 
        // default child iterator impl
    }
}

public class Node : IHaveChildren, IChildIterator
{ 
    // *snip*

    public IEnumerable<IChild> GetChildren()
    {
        return new DefaultChildIterator(this).GetChildren();
    }
}
于 2008-08-18T07:21:20.140 回答
1

Rob Connery(Subsonic 和 MVC Storefront)在他的 Storefront 应用程序中实现了一个类似 IRepository 的模式。这与上面的模式不太一样,但确实有一些相似之处。

数据层返回 IQueryable,它允许消费层在此之上应用过滤和排序表达式。例如,好处是能够指定单个 GetProducts 方法,然后在消费层中适当地决定您希望如何排序、过滤甚至只是特定范围的结果。

不是传统的方法,但非常酷,绝对是 DRY 的一个案例。

于 2008-10-15T20:32:03.143 回答
0

我可以看到的一个问题是,在一家大公司中,这种模式可能会使代码变得难以(如果不是不可能的话)任何人都难以理解和使用。如果多个开发人员不断地将他们自己的方法添加到现有的类中,与这些类分开(上帝帮助我们所有人,甚至是 BCL 类),我可能会看到代码库很快失控。

即使在我自己的工作中,我也能看到这种情况发生,我的 PM 希望将我们处理的每一段代码添加到 UI 或数据访问层,我完全可以看到他坚持要添加 20 或 30 种方法System.String 仅与字符串处理相切相关。

于 2008-08-11T18:21:37.283 回答
0

我需要解决类似的问题:我想将 List<IIDable> 传递给扩展函数,其中 IIDable 是一个具有长 getId() 函数的接口。我尝试使用 GetIds(this List<IIDable> bla) 但编译器不允许我这样做。我改用模板,然后在函数内部将类型转换为接口类型。对于一些 linq to sql 生成的类,我需要这个函数。

我希望这有帮助 :)

    public static List<long> GetIds<T>(this List<T> original){
        List<long> ret = new List<long>();
        if (original == null)
            return ret;

        try
        {
            foreach (T t in original)
            {
                IIDable idable = (IIDable)t;
                ret.Add(idable.getId());
            }
            return ret;
        }
        catch (Exception)
        {
            throw new Exception("Class calling this extension must implement IIDable interface");
        }
于 2009-12-07T10:26:19.300 回答