【问题标题】:Calling a function based on parameter type根据参数类型调用函数
【发布时间】:2013-08-11 09:57:16
【问题描述】:

我正在尝试弄清楚如何简化以下内容

假设我有 2 个实体类

public class A
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string City { get; set; }
}

public class B
{
    public int Id { get; set; } 
    public string Nom { get; set; }
    public string Ville { get; set; }
} 

相似但不相同的类。

每个类都有一个用于 CRUD 操作的存储库类,例如...

public class RepA
{
    public static List<A> GetAll()
    {
        List<A> list = new List<A>();

        A a1 = new A() {Id=1, Name="First A", City="Boston"};
        A a2 = new A() {Id=2, Name="First B", City="Chicago"};
        A a3 = new A() {Id=3, Name="First C", City="San Francisco"};

        list.Add(a1);
        list.Add(a2);
        list.Add(a3);
        return list;
    }

    public static void SaveAll(List<A> list)
    {
        foreach (A a in list)
        {
              Console.WriteLine("Saved Id = {0} Name = {1} City={2}", 
                  a.Id, a.Name, a.City);
        }
    }

}

public class RepB
    {
        public static List<B> GetAll()
        {
            List<B> list = new List<B>();

            B b1 = new B() {Id=1, Nom="Second A", Ville="Montreal"};
            B b2 = new B() {Id=2, Nom="Second B", Ville="Paris"};
            B b3 = new B() {Id=3, Nom="Second C", Ville="New Orleans"};

            list.Add(b1);
            list.Add(b2);
            list.Add(b3);
            return list;
        }

    public static void SaveAll(List<B> list)
    {
        foreach (B b in list)
        {
            Console.WriteLine("Saved Id = {0} Name = {1} City={2}", b.Id, 
                    b.Nom, b.Ville);
        }
    }

}

我将如何匿名调用我的存储库而不必诉诸于此,因为在我的真实示例中,我有 100 个存储库,而不是 2 个。

void Main()
{
    ChosenType chosentype    = RandomChosenType(); //A or B
    switch (chosentype)
    {
        case ChosenType.A:
            var listA = RepA.GetAll();
            RepA.SaveAll(listA);
            break;
        case ChosenType.B:
            var listB = RepB.GetAll();
            RepB.SaveAll(listB);
            break;
            default:
            break;
    }
}

【问题讨论】:

  • 看起来您正试图为每个本地化存储一个单独的表 - 这不是一个好主意。最好在后端保持一致,并且仅出于显示目的进行本地化。
  • 本地化数据结构似乎是个坏主意。不知道你会从中获得什么。它只会使您的代码难以编写和维护。程序员真的希望能够阅读所有语言吗?
  • 获取所有的类,把它放在一个列表中,并根据选择的类调用常用方法,在 Switch 中传递一个并将 LIST 作为通用关键字并附加列表 +"ClassName" ...
  • 什么是ChosenType?你真的有每种类型的枚举吗?类型是如何确定的(换句话说,RandomChosenType 代表什么)?
  • 我的示例显示本地化,但忽略字段的名称,我试图在我的示例中快速。但表格包含不同的列和类型,彼此没有本地化版本

标签: c# .net list generics interface


【解决方案1】:

创建base class 或使用interface

public interface IBase<T>
{
     List<T> GetAll();
     void SaveAll(List<T> items);
}

public class RepA : IBase<RepA> 
{
    public List<RepA> GetAll() { return new List<RepA>(); }
    public void SaveAll(List<RepA> repA) { }
}

public class RepB : IBase<RepB> 
{
    public List<RepB> GetAll() { return new List<RepB>(); }
    public void SaveAll(List<RepB> repB) { }
}

void Main() 
{
    IBase chosenType = RandomChosenType();
    var list = chosenType.GetAll();
}

【讨论】:

  • 这仅适用于IBase 是通用的,GetAll/SaveAll 使用强类型列表。
  • GetAll 应该返回 List
  • @BlackBear - 调整。
  • 这行不通。需要有一个非泛型 IBase 返回一个非泛型列表。否则RandomChosenType 也需要是泛型的并使用特定类型调用,这与目的不符。
  • 我不知道我是否理解。 GetAll 现在返回一个 RepA 列表,它是我的存储库类,而不是 A,它是我的实体类...
【解决方案2】:

您可以使您的存储库实现一个接口,例如IGetAllSaveAll。然后,您可以将存储库存储在列表中,并将它们投射到该接口。这样,您就可以在所有这些上调用 GetAll 函数: (其实第一个接口不是强制的,可以直接写成IEnumerable&lt;object&gt; GetAll()...)

interface IGetAllSaveAll<T>
{
    IEnumerable<T> GetAll();
    void SaveAll(IEnumerable<T> obj);
}

您需要有一个基本接口:

interface IGetAllSaveAll : IGetAllSaveAll<object>

并使用它:

public class RepA: IGetAllSaveAll
public class RepB: IGetAllSaveAll
....

然后你可以在某个地方保存所有这些存储库的字典:

Dictionnary<Type, IGetAllSaveAll> myDic;

当然,您仍然需要将存储库添加到您的字典中:

myDic.Add(typeof(A), new RepA());

然后调用它:

Type t = RandomChosenType();
myDic[t].GetAll();

【讨论】:

  • 在这种情况下假设IGetAllSaveAll&lt;IEnumerable&lt;T&gt;&gt; 可能是安全的。
  • @James 实际上我做到了IEnumerable 但在方法上,似乎更好
  • 哎呀,那是我的错误,这才是我真正的意思哈哈!
  • ppetrov,我修改了我的代码以实现您建议的解决方案。我现在遇到的问题是我不能让我的方法保持静态,我需要创建一个存储库的实例。除非我遗漏了什么,否则在我看来,这就像我只是将开关移到更远的地方
  • @RedSoxFred 对不起,我的错误,没有看到 static 关键字...如果您对这些存储库使用单例模式,这仍然可以工作
【解决方案3】:

根据反射和对类结构的一些假设尝试这种方法:

static void Main(string[] args)
{
    var types = Assembly.GetExecutingAssembly().Modules
        .SelectMany(m => m.GetTypes())
        .Where(t =>
            t.GetMethod("GetAll") != null &&
            t.GetMethod("SaveAll") != null &&
            t.GetMethod("GetAll").ReturnType.IsGenericType)
        .Select(t =>
            new
            {
                RepositoryType = t,
                ReturnTypeArgument = 
                    t.GetMethod("GetAll").ReturnType.GenericTypeArguments[0]
            }
            )
        .ToList();

    (new List<dynamic> { new A(), new B() }).ToList().ForEach(chosenType =>
    {
        var association = types
            .FirstOrDefault(t => 
                t.ReturnTypeArgument == chosenType.GetType());
        if (association == null)
            return;
        var repType = association.RepositoryType;
        dynamic list = repType.GetMethod("GetAll")
            .Invoke(chosenType, new object[] { });
        repType.GetMethod("SaveAll")
            .Invoke(chosenType, new object[] { list });
    });
}

【讨论】:

    【解决方案4】:

    您发布的代码使用静态方法。为了实现接口,您将需要实例方法。除非你想使用反射(在我看来应该避免),否则这些方法需要不知道类型。像这样的:

    public interface IRepository {
        IEnumerable<object> GetAll();
    }
    

    在 RepA 中:

    IEnumerable<object> IRepository.GetAll() {
        return RepA.GetAll();
    }
    

    您的每个菜单选择都可以在IRepository 类型的字段中仅包含适当存储库类的实例,而不是存储类型。在其中一个实例上调用 GetAll 后,您可以稍后根据需要将结果转换为特定类型(如 List&lt;A&gt;)。

    【讨论】:

    • 我想我们可能会在这里做点什么:) 让我试试
    【解决方案5】:

    您应该使用单个通用存储库。操作应由注入的委托处理。存储库可能如下所示:

    public class GenericRepositoryExample
    {
    
        public void Save<T>(IList<T> persons, SaveDelegate<T> save)
        {
            foreach (T person in persons)
            {
                Console.WriteLine(save(person));
            }
        }
    }
    

    请注意,保存委托被传递给 Save 方法。您示例中的 SaveDelegate 可以声明为:

    public delegate string SaveDelegate<T>(T input);
    

    为方便起见,我创建了一个包含委托函数的 HelperClass。如果可能,通常应避免在现实生活中使用辅助类。

    public static class HelperClass
    {
        public static string FrenchSave(B frenchInput)
        {
    
            string result = string.Format("ID = {0}; Name = {1}; City = {2}", frenchInput.Id, frenchInput.Nom, frenchInput.ville);
            return result;
        }
    
        public static string EnglishSave(A englishInput)
        {
            string result = string.Format("ID = {0}; Name = {1}; City = {2}", englishInput.Id, englishInput.name, englishInput.city);
            return result;
        }
    
    }
    

    为了说明此设置的使用,我创建了以下单元测试:

      [Test]
        public void TestGenericRepository()
        {
            IList<A> aList = new List<A>();
    
            aList.Add(new A() { Id = 1, name = "George", city = "Chicago"});
            aList.Add(new A() { Id = 2, name = "Bill", city = "Toledo" });
    
    
            List<B> bList = new List<B>(); 
    
            bList.Add(new B() {Id= 1, Nom = "Nathalie", ville = "Paris"});
            bList.Add(new B() {Id = 2, Nom = "Michelle", ville = "Lyon"});
    
    
            GenericRepositoryExample repository = new GenericRepositoryExample();
    
            repository.Save<A>(aList,HelperClass.EnglishSave);
    
            repository.Save<B>(bList,HelperClass.FrenchSave);
    
        }
    

    【讨论】:

    • 好,但我仍然需要知道 A 和类型......所以如果我有 100 个存储库,我的实现仍然需要 100 个不同的调用,或者我错过了什么我想要的是允许我编写 Save(list) 并基于列表中的 的设置,它将知道要调用哪个保存并在没有开关的情况下执行此操作
    • @RedSoxFred。我不确定你的意思。您希望能够保存包含 A 和 B 的列表吗?
    • 我也想要保存,在运行时根据列表中的实体类型选择需要调用的保存方法
    • 这就是我的示例所做的。只要你的 A、B 和其他类没有公共接口,你就需要为每个类保存。
    【解决方案6】:

    根据您的确切情况,您有一个代表每种可能数据类型的枚举,这可能会起作用。

    使用属性将每个枚举值映射到存储库类型。每个存储库都继承自一个泛型类,该类实现了一个非强类型的基本接口。 repo 方法从静态成员变为实例成员。基础 repo 类必须进行强制转换以将 object 转换为适当的类型并返回,但实际的存储库实现是强类型的。

    您可以更进一步,尝试使用表达式树缓存一些反射,这样您只需执行一次,但这取决于您真正需要进行的优化程度。

    public enum ChosenType {
        [Repo(typeof(RepA))] A = 0,
        [Repo(typeof(RepB))] B = 1
    }
    
    public class RepoAttribute : Attribute {
        public RepoAttribute(Type repoType) { RepoType = repoType; }
        public Type RepoType { get; set; }
    }
    
    class Program
    {
        static void Main()
        {
            ChosenType chosentype = RandomChosenType(); //A or B
    
            // Make an instance of the appropriate repo based on the mapping
            // to the enum value.
            // This is a moderately expensive call, and there's room for improvement
            // by using expression trees and caching lambda expressions.
            var repo = (IRepo)Activator.CreateInstance(
                ((RepoAttribute)typeof(ChosenType).GetMember(chosentype.ToString())
                    .Single().GetCustomAttributes(typeof(RepoAttribute), false).Single()
                ).RepoType);
    
            var list = repo.GetAll();
            repo.SaveAll(list);
    
            Console.Read();
        }
    
        static Random _rand = new Random();
        static ChosenType RandomChosenType()
        {
            return (ChosenType)_rand.Next(0, 2);
        }
    }
    
    public class A { /* No change */ }
    public class B { /* No change */ }
    
    public interface IRepo {
        List<object> GetAll();
        void SaveAll(List<object> list);
    }
    
    public abstract class Repo<T> : IRepo {
        List<object> IRepo.GetAll() {
            return GetAll().Cast<object>().ToList();
        }
    
        void IRepo.SaveAll(List<object> list) {
            SaveAll(list.Cast<T>().ToList());
        }
    
        public abstract List<T> GetAll();
        public abstract void SaveAll(List<T> list);
    }
    
    public class RepA : Repo<A> {
        public override List<A> GetAll() { /* No change except the signature */ }
        public override void SaveAll(List<A> list) { /* No change except the signature */ }
    }
    public class RepB : Repo<B> {
        public override List<B> GetAll() { /* No change except the signature */ }
        public override void SaveAll(List<B> list) { /* No change except the signature */ }
    }
    

    【讨论】:

      最近更新 更多