如何创建可扩展的API,仍然使用对象初始化器语法?

时间:2010-12-04 15:33:55

标签: c# extension-methods extensibility api-design object-initializers

我有一个包装Mercurial命令行客户端的类库。

我的目的是实现对所有内置命令的支持,但除此之外,还有大量的扩展。

所以我需要让我的库可扩展,因为我和其他人都可以添加对扩展的支持。我计划为一些更受欢迎和典型的扩展添加支持(至少有很多与Mercurial捆绑的扩展),但我仍然希望能够从外部扩展它。

目前,命令的语法如下所示:

Repo.Execute(new CommitCommand
{
    Message = "Your commit message",
    AddRemove = true,
});

然而,如果没有程序员认为扩展只是部分修改,这对于扩展来说并不容易。

例如,假设我公开了一个额外命令行参数的公共集合,我可以手动执行此操作:

var cmd = new CommitCommand
{
    Message = "Your commit message",
    AddRemove = true,
};
cmd.Arguments.Add("--my-commit-extension");
Repo.Execute(cmd);

我似乎没有简单的方法来添加额外的扩展,以便可以将其设置为对象初始值设定项的一部分。

我一直在考虑添加或者转换为流畅的界面语法。在这种情况下,你可以这样写:

Repo.Execute(new CommitCommand()
    .Message("Your commit message")
    .AddRemove()
    .MyCommitExtension());

但是,我看到人们不喜欢流畅的界面,他们觉得自己变得过于健谈。

我还有其他选择吗?

我想要的,基本上:

  • 一种常见的语法风格
    • 对于内置的东西
    • 以及我图书馆用户添加的扩展程序

我设想我的库的用户会通过添加新类和扩展方法来扩展它以获得intellisense支持,但是扩展方法不能在对象初始化器中使用,这意味着所有扩展看起来都像是事后的想法。那不是我想要的。

欢迎任何想法。

3 个答案:

答案 0 :(得分:1)

我对Mercurial并不熟悉,你的问题似乎过于笼统,无法具体解决,但我可以解决一个特别的评论。

var cmd = new CommitCommand 
{ 
    Message = "Your commit message", 
    AddRemove = true, 
}; 
cmd.Arguments.Add("--my-commit-extension"); 
Repo.Execute(cmd); 

如果CommitCommand.Arguments为IList<T>,您已经能够使用初始化语法:

class CommitCommand
{
    public string Message { get; set; }
    public bool AddRemove { get; set; }
    public List<string> Arguments = new List<string>();
}

Repo.Execute(new CommitCommand
{
    Message = "Your commit message",
    AddRemove = true,
    Arguments = { "--my-commit-extension", "--my-other-commit-extension" }
});

答案 1 :(得分:1)

我也不熟悉Mercurial,但有一种选择是抛出intellisense并使用匿名类型:

Repo.Execute(new 
{
    Message = "Your commit message",
    AddRemove = true,
    MyCommitExtension = null
});

您不能在属性名称中使用连字符,因此您需要从PascalCase替换为连字符小写。或者你可以用夸张的替换下划线。

我不确定我是否推荐这种方法,而不了解人们使用这些扩展的频率。在处理HTML属性时,这种方法在ASP.NET MVC框架中运行良好,但该方案的不同之处在于框架不需要处理值,它只是将它们直接写入输出。如果您根据以这种方式提供的值进行条件操作,或者您希望您的API对于那些不了解Mercurial命令行语法的人更容易被发现,那么您可以尝试其他方法。

答案 2 :(得分:0)

至于我流利的界面还可以。但如果你想避免它,那么我可能会像这样使用smth:

interface IExtension { ... }

class SomeExtension1 : IExtension { ... }
class SomeExtension2 : IExtension { ... }

class CommitCommand
{
    public string Message;
    public bool AddRemove;
    public readonly IList<IExtension> Extensions = new List<IExtension>();
}

允许以这种方式使用命令:

new CommitCommand
{
    Message = "",
    AddRemove = true,
    Extensions = {new SomeExtension1(), new SomeExtension2()}
};