C# 扩展接口模式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8042/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Extension interface patterns
提问by Keith
The new extensions in .Net 3.5 allow functionality to be split out from interfaces.
.Net 3.5 中的新扩展允许从接口中分离功能。
For instance in .Net 2.0
例如在 .Net 2.0
public interface IHaveChildren {
string ParentType { get; }
int ParentId { get; }
List<IChild> GetChildren()
}
Can (in 3.5) become:
可以(在 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
}
}
This seems to me to be a better mechanism for many interfaces. They no longer need an abstract base to share this code, and functionally the code works the same. This could make the code more maintainable and easier to test.
在我看来,这对于许多接口来说是一种更好的机制。他们不再需要抽象基础来共享此代码,并且代码的功能相同。这可以使代码更易于维护和更易于测试。
The only disadvantage being that an abstract bases implementation can be virtual, but can that be worked around (would an instance method hide an extension method with the same name? would it be confusing code to do so?)
唯一的缺点是抽象基础实现可以是虚拟的,但可以解决这个问题(实例方法会隐藏具有相同名称的扩展方法吗?这样做会混淆代码吗?)
Any other reasons not to regularly use this pattern?
还有其他不经常使用这种模式的原因吗?
Clarification:
澄清:
Yeah, I see the tendency with extension methods is to end up with them everywhere. I'd be particularly careful having any on .Net value types without a great deal of peer review (I think the only one we have on a string is a .SplitToDictionary()
- similar to .Split()
but taking a key-value delimiter too)
是的,我看到扩展方法的趋势是最终到处都是它们。我会特别小心在没有大量同行评审的情况下使用任何 .Net 值类型(我认为我们对字符串的唯一一个是.SplitToDictionary()
- 类似于.Split()
但也使用键值分隔符)
I think there's a whole best practice debate there ;-)
我认为那里有一个完整的最佳实践辩论;-)
(Incidentally: DannySmurf, your PM sounds scary.)
(顺便说一句:DannySmurf,你的 PM 听起来很吓人。)
I'm specifically asking here about using extension methods where previously we had interface methods.
我在这里特别询问在以前我们有接口方法的地方使用扩展方法。
I'm trying to avoid lots of levels of abstract base classes - the classes implementing these models mostly already have base classes. I think this model could be more maintainable and less overly-coupled than adding further object hierarchies.
我试图避免很多级别的抽象基类——实现这些模型的类大多已经有了基类。我认为这个模型可能比添加更多的对象层次结构更易于维护并且不会过度耦合。
Is this what MS has done to IEnumerable and IQueryable for Linq?
这是 MS 对 Linq 的 IEnumerable 和 IQueryable 所做的吗?
采纳答案by Emperor XLII
I think the judicious use of extension methods put interfaces on a more equatable position with (abstract) base classes.
我认为明智地使用扩展方法使接口与(抽象)基类处于更平等的位置。
Versioning.One advantage base classes have over interfaces is that you can easily add new virtual members in a later version, whereas adding members to an interface will break implementers built against the old version of the library. Instead, a new version of the interface with the new members needs to be created, and the library will have to work around or limit access to legacy objects only implementing the original interface.
版本控制。基类相对于接口的一个优势是您可以轻松地在更高版本中添加新的虚拟成员,而向接口添加成员会破坏针对旧版本库构建的实现者。相反,需要创建具有新成员的新版本接口,并且库必须解决或限制对仅实现原始接口的遗留对象的访问。
As a concrete example, the first version of a library might define an interface like so:
作为一个具体的例子,库的第一个版本可能会定义一个接口,如下所示:
public interface INode {
INode Root { get; }
List<INode> GetChildren( );
}
Once the library has released, we cannot modify the interface without breaking current users. Instead, in the next release we would need to define a new interface to add additional functionalty:
一旦库发布,我们不能在不破坏当前用户的情况下修改界面。相反,在下一个版本中,我们需要定义一个新接口来添加附加功能:
public interface IChildNode : INode {
INode Parent { get; }
}
However, only users of the new library will be able to implement the new interface. In order to work with legacy code, we need to adapt the old implementation, which an extension method can handle nicely:
但是,只有新库的用户才能实现新界面。为了处理遗留代码,我们需要调整旧的实现,扩展方法可以很好地处理:
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 );
}
}
Now all users of the new library can treat both legacy and modern implementations identically.
现在,新库的所有用户都可以同等对待遗留和现代实现。
Overloads.Another area where extension methods can be useful is in providing overloads for interface methods. You might have a method with several parameters to control its action, of which only the first one or two are important in the 90% case. Since C# does not allow setting default values for parameters, users either have to call the fully parameterized method every time, or every implementation must implement the trivial overloads for the core method.
超载。扩展方法有用的另一个领域是为接口方法提供重载。您可能有一个带有多个参数的方法来控制其操作,在 90% 的情况下,其中只有前一个或两个是重要的。由于 C# 不允许为参数设置默认值,因此用户要么每次都必须调用完全参数化的方法,要么每次实现都必须为核心方法实现琐碎的重载。
Instead extension methods can be used to provide the trivial overload implementations:
相反,扩展方法可用于提供简单的重载实现:
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 );
}
...
}
Please note that both of these cases are written in terms of the operations provided by the interfaces, and involve trivial or well-known default implementations. That said, you can only inherit from a class once, and the targeted use of extension methods can provide a valuable way to deal with some of the niceties provided by base classes that interfaces lack :)
请注意,这两种情况都是根据接口提供的操作编写的,并且涉及琐碎或众所周知的默认实现。也就是说,你只能从一个类继承一次,并且有针对性地使用扩展方法可以提供一种有价值的方法来处理接口缺乏的基类提供的一些细节:)
Edit:A related post by Joe Duffy: Extension methods as default interface method implementations
编辑:Joe Duffy 的一篇相关文章:Extension methods as default interface method implementations
回答by TheSmurf
One problem I can see is that, in a large company, this pattern could allow the code to become difficult (if not impossible) for anyone to understand and use. If multiple developers are constantly adding their own methods to existing classes, separate from those classes (and, God help us all, to BCL classes even), I could see a code base spinning out of control rather quickly.
我可以看到的一个问题是,在一家大公司中,这种模式可能会使代码变得难以(如果不是不可能)任何人理解和使用。如果多个开发人员不断地将他们自己的方法添加到现有的类中,与这些类分开(上帝帮助我们所有人,甚至是 BCL 类),我可能会看到代码库很快失控。
Even at my own job, I could see this happening, with my PM's desire to add every bit of code that we work on to either the UI or the data access layer, I could totally see him insisting on 20 or 30 methods being added to System.String that are only tangentially-related to string handling.
即使在我自己的工作中,我也可以看到这种情况发生,因为我的 PM 希望将我们处理的每一段代码添加到 UI 或数据访问层,我完全可以看到他坚持要添加 20 或 30 个方法System.String 仅与字符串处理相关。
回答by Vaibhav
I think the best thing that extension methods replace are all those utility classes that you find in every project.
我认为扩展方法取代的最好的东西是您在每个项目中找到的所有实用程序类。
At least for now, I feel that any other use of Extension methods would cause confusion in the workplace.
至少就目前而言,我觉得任何其他使用 Extension 方法都会在工作场所造成混乱。
My two bits.
我的两点。
回答by Jon Limjap
Extension methods should be used as just that: extensions. Any crucial structure/design related code or non-trivial operation should be put in an object that is composed into/inherited from a class or interface.
扩展方法应该用作:扩展。任何关键的结构/设计相关代码或非平凡操作都应该放在一个由类或接口组成/继承的对象中。
Once another object tries to use the extended one, they won't see the extensions and might have to reimplement/re-reference them again.
一旦另一个对象尝试使用扩展的对象,他们将看不到扩展并且可能不得不再次重新实现/重新引用它们。
The traditional wisdom is that Extension methods should only be used for:
传统观点认为扩展方法应该只用于:
- utility classes, as Vaibhav mentioned
- extending sealed 3rd party APIs
- 实用程序类,正如 Vaibhav 提到的
- 扩展密封的第 3 方 API
回答by Lars M?hlum
Ouch. Please don't extend Interfaces.
An interface is a clean contract that a class should implement, and your usage of said classes mustbe restricted to what is in the core Interface for this to work correctly.
哎哟。请不要扩展接口。
接口是一个类应该实现的干净契约,并且您对所述类的使用必须限制在核心接口中的内容才能正常工作。
That is why you always declare the interface as the type instead of the actual class.
这就是为什么你总是将接口声明为类型而不是实际的类。
IInterface variable = new ImplementingClass();
Right?
对?
If you really need a contract with some added functionality, abstract classes are your friends.
如果你真的需要一个带有一些附加功能的合约,抽象类就是你的朋友。
回答by Scott Dorman
There is nothing wrong with extending interfaces, in fact that is how LINQ works to add the extension methods to the collection classes.
扩展接口并没有错,事实上这就是 LINQ 将扩展方法添加到集合类的工作方式。
That being said, you really should only do this in the case where you need to provide the same functionality across all classes that implement that interface and that functionality is not (and probably should not be) part of the "official" implementation of any derived classes. Extending an interface is also good if it is just impractical to write an extension method for every possible derived type that requires the new functionality.
话虽如此,您真的应该只在需要为实现该接口的所有类提供相同功能的情况下才这样做,并且该功能不是(并且可能不应该是)任何派生的“官方”实现的一部分类。如果为需要新功能的每个可能的派生类型编写扩展方法是不切实际的,那么扩展接口也很好。
回答by Zooba
I see separating the domain/model and UI/view functionality using extension methods as a good thing, especially since they can reside in separate namespaces.
我认为使用扩展方法分离域/模型和 UI/视图功能是一件好事,特别是因为它们可以驻留在单独的命名空间中。
For example:
例如:
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;
}
}
}
This way extension methods can be used similarly to XAML data templates. You can't access private/protected members of the class but it allows the data abstraction to be maintained without excessive code duplication throughout the application.
通过这种方式,可以类似于 XAML 数据模板使用扩展方法。您无法访问该类的私有/受保护成员,但它允许维护数据抽象,而不会在整个应用程序中产生过多的代码重复。
回答by Fredrik Kalseth
I see a lot of people advocating using a base class to share common functionality. Be careful with this - you should favor composition over inheritance. Inheritance should only be used for polymorphism, when it makes sense from a modelling point of view. It is not a good tool for code reuse.
我看到很多人提倡使用基类来共享通用功能。小心这一点 - 你应该更喜欢组合而不是继承。继承应该只用于多态,从建模的角度来看它是有意义的。它不是代码重用的好工具。
As for the question: Be ware of the limitations when doing this - for example in the code shown, using an extension method to implement GetChildren effectively 'seals' this implementation and doesn't allow any IHaveChildren impl to provide its own if needed. If this is OK, then I dont mind the extension method approach that much. It is not set in stone, and can usually be easily refactored when more flexibility is needed later.
至于问题:请注意执行此操作时的限制 - 例如在显示的代码中,使用扩展方法来实现 GetChildren 有效地“密封”了此实现,并且不允许任何 IHaveChildren impl 在需要时提供自己的实现。如果这没问题,那么我不太介意扩展方法的方法。它不是一成不变的,通常可以在以后需要更多灵活性时轻松重构。
For greater flexibility, using the strategy pattern may be preferable. Something like:
为了获得更大的灵活性,使用策略模式可能更可取。就像是:
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();
}
}
回答by Scott McKenzie
Rob Connery (Subsonic and MVC Storefront) implemented an IRepository-like pattern in his Storefront application. It's not quite the pattern above, but it does share some similarities.
Rob Connery(Subsonic 和 MVC Storefront)在他的 Storefront 应用程序中实现了一个类似 IRepository 的模式。这与上面的模式不太一样,但确实有一些相似之处。
The data layer returns IQueryable which permits the consuming layer to apply filtering and sorting expression on top of that. The bonus is being able to specify a single GetProducts method, for example, and then decide appropriately in the consuming layer how you want that sorting, filtering or even just a particular range of results.
数据层返回 IQueryable,它允许消费层在其之上应用过滤和排序表达式。例如,好处是能够指定单个 GetProducts 方法,然后在消费层中适当地决定您希望如何排序、过滤甚至只是特定范围的结果。
Not a traditional approach, but very cool and definitely a case of DRY.
不是传统的方法,但非常酷,绝对是 DRY 的一个案例。
回答by Abdo
I needed to solve something similar: I wanted to have a List<IIDable> passed to the extensions function where IIDable is an interface that has a long getId() function. I tried using GetIds(this List<IIDable> bla) but the compiler didn't allow me to do so. I used templates instead and then type casted inside the function to the interface type. I needed this function for some linq to sql generated classes.
我需要解决类似的问题:我想将 List<IIDable> 传递给扩展函数,其中 IIDable 是一个具有长 getId() 函数的接口。我尝试使用 GetIds(this List<IIDable> bla) 但编译器不允许我这样做。我改用模板,然后将函数内部的类型转换为接口类型。对于某些 linq to sql 生成的类,我需要这个函数。
I hope this helps :)
我希望这有帮助 :)
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");
}