【问题标题】:Extending a C# interface with common logic without using Extension Methods使用通用逻辑扩展 C# 接口而不使用扩展方法
【发布时间】:2014-06-10 08:56:33
【问题描述】:

有没有办法在不使用扩展方法的情况下扩展接口?

如果我用几个 get/set 字符串定义了一些接口,例如:

public interface IMyItem
{
    string Title { get; set; }
    string Description { get; set; }
}

我想为这些接口添加一些简单的验证,但没有 重新定义逻辑或强制某种形式的继承。

目前我正在使用扩展方法,如下所示:

public static class MyItemExtensions
{
    public static bool ERROR(this IMyItem item)
    {
        return item.TITLE_ERROR() || item.DESCRIPTION_ERROR();
    }

    public static bool TITLE_ERROR(this IMyItem item)
    {
        return string.IsNullOrEmpty(item.Title);
    }

    public static bool DESCRIPTION_ERROR(this IMyItem item)
    {
        return string.IsNullOrEmpty(item.Description);
    }
}

这样做很有效,我可以:

public class Item : IMyItem
{
   public string Title { get; set; }
   public string Description { get; set; }
}

public static class app
{
    public static void go()
    {
        var item = new Item
        {
            Title = "My Item Title", 
            Description = ""
        };

        Console.log(item.ERROR());
    }
}

但我更喜欢ERRORTITLE_ERRORDESCRIPTION_ERROR 要获取/设置 - 有没有办法实现相同但公开 获取/设置属性而不是扩展方法?

2014 年 11 月 6 日更新

正如许多人所建议的,abstract 类将是基于示例的明显解决方案,但类型需要实现 多个 接口。

虽然可以安排继承,但对类型来说是不必要的复杂性和限制。

使用扩展方法在这些接口上进行验证的额外好处允许通过命名空间使用上下文特定和共享逻辑。

可以在不同的命名空间上为接口定义多个ERROR(this IMyItem item) 扩展方法。一个检查TITLE_ERRORDESCRIPTION_ERROR 而另一个可能只测试其中一个属性。然后,根据上下文,可以引用相关的命名空间并执行该项目的共享验证。

我会看看微软的验证器,但它看起来相当冗长,我真的希望这些状态作为类型的属性,因为它使使用它们的代码更容易使用。

此外,这些都是非常简单的示例,有些验证要复杂得多,有些情况需要与其他 Web 服务交互 - 尽管针对 AppDomain 中的 Web 服务数据缓存。

目前这些interface 扩展方法感觉是最好的解决方案。

【问题讨论】:

  • 并非如此。但你为什么在乎?属性只不过是 getter/setter 方法周围的糖。
  • 也许你可以用abstract类代替,将cTitlecDescription这两个属性抽象化,然后在同一个抽象类中包含错误方法?
  • Extending an Interface的可能重复
  • @TzahMama 我不这么认为。链接的问题是关于在单个类中实现两个具有相同签名的接口方法。这个是关于代码共享的。
  • @Luaan 正如你所说,get/set 与扩展方法只不过是语法糖而已。在这种情况下,我宁愿它是一件小毛衣。

标签: c# .net c#-4.0 interface extension-methods


【解决方案1】:

我认为正确的解决方案是使用abstract class 而不是interface

您在这里分享的是一个通用的验证逻辑,它对任何实现IMyItem 的类都有效。因此,我建议您创建一个 abstract class 作为所有项目的基础,这样他们都可以重用该验证代码。您甚至可以将这些属性设为虚拟,因此可以扩展该验证代码:

public abstract class ItemBase : IMyItem
{
    public string cTitle { get; set; }
    public string cDescription { get; set; }

    public virtual bool Error 
    { 
      get { return TitleError || DescriptionError; } 
    }

    public virtual bool TitleError 
    { 
      get { return string.IsNullOrEmpty(cTitle); } 
    }

    public virtual bool DescriptionError 
    { 
      get { return string.IsNullOrEmpty(cDescription); } 
    }
}

【讨论】:

  • 谢谢@Yuval!正如您所说,abstract 将适用,但类型必须实现多个接口,并且深度继承会给类型增加不必要的复杂性。感谢您的建议。
  • 深度继承如何?类型可以实现多个接口,而无需考虑从公共基类派生
  • 当然,这并不深,它是单个项目/接口/抽象类的简化示例。但是,IRL 更高级别的类实现了任意数量的这些接口,每个接口都有这些扩展方法。为了适应所有这些不同的类型,我需要不同的抽象类。作为一个类不能从多个抽象类继承,我将不得不添加某种形式的继承深度。这给类型增加了不必要的复杂性。
  • 我明白了。好吧,下次我建议您发布您的 IRL 问题/代码,以便我们根据实际问题进行回答。
  • 会的!再次感谢@yuval-itzchakov :)
【解决方案2】:

您似乎在重新发明轮子。 Microsoft 已经创建了一个相当 good validation framework,它不需要实体框架或 MVC。只需将System.ComponentModel.DataAnnotationsusing System.ComponentModel.DataAnnotations 的引用添加到一个类:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

public class Program
{
    public static void Main()
    {
        // required if you use the MetdataType attribute
        TypeDescriptor.AddProviderTransparent(
            new AssociatedMetadataTypeTypeDescriptionProvider(typeof(MyItem),
                typeof(IMyItemValidation)),
            typeof(MyItem));


        var item = new MyItem();
        var context = new ValidationContext(item, 
          serviceProvider: null, 
          items: null);
        var results = new List<ValidationResult>();

        var isValid = Validator.TryValidateObject(item, context, results);

        if (!isValid)
        {
            foreach (var validationResult in results)
            {
                Console.WriteLine(validationResult.ErrorMessage);
            }
        }

        Console.ReadKey();
    }

    [MetadataType(typeof(IMyItemValidation))]
    public class MyItem : IMyItem
    {
        public string cTitle { get; set; }
        public string cDescription { get; set; }
    }

    public interface IMyItem
    {
        string cTitle { get; set; }
        string cDescription { get; set; }
    }

    public interface IMyItemValidation
    {
        [Required]
        string cTitle { get; set; }
        [Required]
        string cDescription { get; set; }
    }

    /* 

    // alternatively you could do either of these as well:
    // Derive MyItem : MyItemBase

    // contains the logic on the base class
    public abstract MyItemBase
        [Required]
        public string cTitle { get; set; }
        [Required]
        public string cDescription { get; set; }
    }

    // or

    // Derive MyItem : MyItemBase
    // contains the logic on the base class using MetadataType
    [MetadataType(typeof(IMyItemValidation))]
    public abstract MyItemBase
        public string cTitle { get; set; }
        public string cDescription { get; set; }
    }


}

输出

cTitle 字段是必需的。

cDescription 字段是必需的。

【讨论】:

  • 谢谢埃里克。我会看一下验证器,但它看起来相当冗长——我是在删除括号之后,而不是为验证结果添加额外的类型。 - 我也更新了我的问题。
猜你喜欢
  • 2016-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多