代码之家  ›  专栏  ›  技术社区  ›  sbi

用.NET泛型改进参数可变的方法

  •  4
  • sbi  · 技术社区  · 14 年前

    我有很多功能,目前都超负荷运行 int string :

    bool foo(int);
    bool foo(string);
    bool bar(int);
    bool bar(string);
    void baz(int p);
    void baz(string p);
    

    然后,我有很多函数接受其中一个的1、2、3或4个参数 int 一串 ,调用上述函数:

    void g(int p1)    { if(foo(p1)) baz(p1); }
    void g(string p1) { if(foo(p1)) baz(p1); }
    
    void g(int    p2, int    p2) { if(foo(p1)) baz(p1); if(bar(p2)) baz(p2); }
    void g(int    p2, string p2) { if(foo(p1)) baz(p1); if(bar(p2)) baz(p2); }
    void g(string p2, int    p2) { if(foo(p1)) baz(p1); if(bar(p2)) baz(p2); }
    void g(string p2, string p2) { if(foo(p1)) baz(p1); if(bar(p2)) baz(p2); }
    
    // etc.
    

    注释 :实施 g() 家庭只是一个例子

    类型多于当前类型 int 一串 可以随时介绍。对于参数多于4的函数也是如此。当前相同函数的数目几乎不可管理。在任意一个维度中再添加一个变量,组合爆炸将是如此巨大,它可能会毁掉应用程序。

    在C++中,我将模板化。 g-() 然后完成。

    我知道.NET泛型是不同的。我已经和他们斗争了两个小时,试图想出一个不涉及复制和粘贴代码的解决方案,但没有用。

    C泛型不需要我为一系列采用三种类型中的任意一种的五个参数的函数键入相同的代码吗?

    我错过了什么?

    编辑: 这些函数用于解析一组参数(当前 int 一串 )从某个来源。想象 bar() baz() 能够阅读两者 int 一串 g-() 指定要分析的参数类型和数目的系列(通过其参数的类型隐式指定)。

    10 回复  |  直到 14 年前
        1
  •  2
  •   Matthew Whited    14 年前

    没有我想的那么完美…但是如果 foo , bar baz 也有通用版本吗?

    static bool foo(int input)
    {
        return input > 5;
    }
    static bool foo(string input)
    {
        return input.Length > 5;
    }
    static void baz(int input)
    {
        Console.WriteLine(input);
    }
    static void baz(string input)
    {
        Console.WriteLine(input);
    }
    static bool foo<T>(T input)
    {
        if (input is int) return foo((int)(object)input);
        if (input is string) return foo((string)(object)input);
        return false;
    }
    static void baz<T>(T input)
    {
        if (input is int) baz((int)(object)input);
        else if (input is string) baz((string)(object)input);
        else throw new NotImplementedException();
    }
    static void g<T>(T input)
    {
        if (foo(input))
            baz(input);
    }
    static void g<T, U>(T input, U inputU)
    {
        g(input);
        g(inputU);
    }
    
        2
  •  5
  •   Jorge Ferreira    14 年前

    考虑在这种情况下使用继承。我想是的 foo , bar baz 是类型的固有属性(在您的例子中是int或string)。如果这不是真的,请更正或评论这个答案。

    using System;
    
    namespace ConsoleApplication3
    {
        abstract class Param
        {
            public abstract bool Foo();
            public abstract bool Bar();
            public abstract void Baz();
    
            public static IntParam Create(int value)
            {
                return new IntParam(value);
            }
    
            public static StringParam Create(string value)
            {
                return new StringParam(value);
            }
        }
    
        abstract class Param<T> : Param {
            private T value;
    
            protected Param() { }
    
            protected Param(T value) { this.value = value; }
    
            public T Value {
                get { return this.value; }
                set { this.value = value; }
            }
        }
    
        class IntParam : Param<int>
        {
            public IntParam() { }
            public IntParam(int value) : base(value) { }
    
            public override bool Foo() { return true; }
            public override bool Bar() { return true; }
    
            public override void Baz()
            {
                Console.WriteLine("int param value is " + this.Value);
            }
        }
    
        class StringParam : Param<string>
        {
            public StringParam() { }
            public StringParam(string value) : base(value) { }
    
            public override bool Foo() { return true; }
            public override bool Bar() { return true; }
    
            public override void Baz()
            {
                Console.WriteLine("String param value is " + this.Value);
            }
        }
    
        class Program
        {
            static void g(Param p1)
            {
                if (p1.Foo()) { p1.Baz(); }
            }
    
            static void g(Param p1, Param p2)
            {
                if (p1.Foo()) { p1.Baz(); }
                if (p2.Bar()) { p2.Baz(); }
            }
    
            static void Main(string[] args)
            {
                Param p1 = Param.Create(12);
                Param p2 = Param.Create("viva");
    
                g(p1);
                g(p2);
                g(p1, p1);
                g(p1, p2);
                g(p2, p1);
                g(p2, p2);
    
                Console.ReadKey();
            }
        }
    }
    

    这将输出:

    int param value is 12
    String param value is viva
    int param value is 12
    int param value is 12
    int param value is 12
    String param value is viva
    String param value is viva
    int param value is 12
    String param value is viva
    String param value is viva
    

    对于新的支持类型,您可以:

    1. 创建一个支持类型和扩展的新类 Param<T> ;
    2. 实施 Foo , Bar Baz 对于那种新的类型;
    3. 创建新的 g 方法(只有一个)具有另一个参数。

    特别是对于3)这将大大减少方法的爆炸。现在你写一个单曲 G 任何给定数量的参数的方法。以前的设计你不得不写, n 参数, 2^n 方法(n=1->2方法,n=2->4方法,n=3->8方法,…)。

        3
  •  4
  •   drharris    14 年前

    你在这里的真正问题很可能是设计问题,而不是一般性的问题。仿制药应该用于那些实际上是不可知论类型的东西,而不是作为一种让生活变得更容易的“一网打尽”。也许可以尝试发布一些您正在使用的实际示例代码,而有些人可能会对如何重新设计解决方案有一个想法,使您能够在不太头疼的情况下对其进行扩展。

    作为一个取笑者,考虑这样的事情:

    public void DoSomethingConditionally<T>(T key, Func<T, bool> BooleanCheck, Action<T> WhatToDo)
    {
        if (BooleanCheck(key)) WhatToDo(key);
    }
    

    你可以这样称呼它:

    DoSomethingConditionally<String>("input", v => v == "hello", s => Console.WriteLine(s));
    

    我在这里使用了lambda表达式,但您也可以轻松地预先定义一些 Func<> 执行一些常用表达式的。这将是一个比方法重载更好的模式,并且将强制您在设计时处理新的输入类型。

        4
  •  1
  •   Luca    14 年前

    使用对象列表。

    在计划时参数数量未知的情况下,只需使用对象列表即可。类似:

    void g(params object[] args) {
        foreach (object arg in args) {
             if ((arg is int) && (foo((int)arg))) baz((int)arg) else
             if ((arg is string) && (foo((string)arg))) baz((string)arg)
        }
    }
    

    (假设你有 bool foo(int) , bool foo(string) ……)

    所以你可以打电话给:

    g(p1, p2);
    g(p1);
    g(p1, p2, p3)
    

    对于任何类型的组合,因为每个引用都是从对象派生的(对象的类型可能比所需的类型多得多,比如int和string,但在将来支持更多其他类型可能很方便)。

    这是可能的,因为您可以在运行时使用反射来识别类型。

    另一种执行操作序列的方法是使用接口,定义在特定条件下对特定对象执行的操作。

    interface IUpdatable {
        void Update(object[] data);
    }
    
    class object1 : IUpdatable { public void Update(object data) { baz(data); } }
    class object2 : IUpdatable { public void Update(object data) { baz(data); } }
    
    void g(params IUpdatable[] args) {
        foreach (IUpdatable arg in args) {
             arg.Update(args);
        }
    }
    

    但是通过这种方式,您必须将p1和p2(同时也是p3)建模为实现接口的对象,这是不可能的。

        5
  •  1
  •   StarBright    14 年前

    我本可以这样做作为对@smink的评论,但我没有足够的代表……

    如果将param基类扩展为具有隐式运算符,则返回到不必在代码中包装内容(尽管运行时仍会产生包装开销)。

    abstract class Param 
    { 
        ...
        public static implicit operator Param(int value)
        { return new IntParam(value); }
    } 
    
        6
  •  1
  •   Stephen Swensen    14 年前

    如果您使用的是C/.NET 4.0,那么您可以使用动态特性实现多个分派,因此您只需要根据参数的数量实现g的单个重载,并且在每个g实现中按类型正确的foo/bar/baz重载将在运行时得到解决。

        void g(dynamic p1) { if (foo(p1)) baz(p1); }
        void g(dynamic p1, dynamic p2) { if (foo(p1)) baz(p1); if (bar(p2)) baz(p2); }
    

    编辑:

    即使您不能使用C/.NET 4.0,您仍然可以使用反射方法。我为double添加了另一个foo/bar/baz重载,显示了这种通用化的效果,并允许您消除重复的g实现。

        bool foo(int p) {Console.WriteLine("foo(int)=" + p); return p == 0;}
        bool foo(string p) {Console.WriteLine("foo(string)=" + p); return p == "";}
        bool foo(double p) { Console.WriteLine("foo(double)=" + p); return p == 0.0; }
    
        bool bar(int p) {Console.WriteLine("bar(int)=" + p); return p == 1;}
        bool bar(string p) { Console.WriteLine("bar(string)=" + p); return p == ""; }
        bool bar(double p) { Console.WriteLine("bar(double)=" + p); return p == 1.1; }
    
    
        void baz(int p) {Console.WriteLine("baz(int)=" + p);}
        void baz(string p) { Console.WriteLine("baz(string)=" + p); }
        void baz(double p) { Console.WriteLine("baz(double)=" + p); }
    
        //these object overloads of foo/bar/baz allow runtime overload resolution
        bool foo(object p)
        {
            if(p == null) //we need the type info from an instance
                throw new ArgumentNullException();
    
            //may memoize MethodInfo by type of p
            MethodInfo mi = typeof(Program).GetMethod(
                "foo", 
                BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, 
                null, 
                new Type[] { p.GetType() }, 
                null
            );
    
            if (mi.GetParameters()[0].ParameterType == typeof(object))
                throw new ArgumentException("No non-object overload found");
    
            return (bool)mi.Invoke(this, new object[] { p });
        }
    
        bool bar(object p)
        {
            if (p == null)
                throw new ArgumentNullException();
    
            MethodInfo mi = typeof(Program).GetMethod(
                "bar",
                BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
                null,
                new Type[] { p.GetType() },
                null
            );
    
            if (mi.GetParameters()[0].ParameterType == typeof(object))
                throw new ArgumentException("No non-object overload found");
    
            return (bool)mi.Invoke(this, new object[] { p });
        }
    
        void baz(object p)
        {
            if (p == null)
                throw new ArgumentNullException();
    
            MethodInfo mi = typeof(Program).GetMethod(
                "baz",
                BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
                null,
                new Type[] { p.GetType() },
                null
            );
    
            if (mi.GetParameters()[0].ParameterType == typeof(object))
                throw new ArgumentException("No non-object overload found");
    
            mi.Invoke(this, new object[] { p });
        }
    
        //now you don't need to enumerate your identical implementations of g by type
        void g(object p1) { if (foo(p1)) baz(p1); }
        void g(object p1, object p2) { if (foo(p1)) baz(p1); if (bar(p2)) baz(p2); }
    
        7
  •  0
  •   Matt Greer    14 年前

    不幸的是,泛型无法处理这种情况。至少,不太好。如果将方法设为泛型,则可以将几乎任何类型的方法传递给它们。对于泛型,没有足够的WHERE子句将其限制为string和int。如果您的方法要在其中执行特定的int/string相关操作,那么泛型根本就不起作用。

    C语言中的泛型不像C++中的模板那么强大,是的,它们有时会引起一些重大的头痛。只是需要时间去适应他们,去感受他们能做和不能做的事情。

        8
  •  0
  •   Sean Clifford    14 年前

    这可能有点费劲,但是在类工作时封装不同的参数类型吗?:

    public abstract class BaseStuff
    {
        public abstract bool Foo();
        public abstract bool Bar();
        public abstract void Baz();
    
        public void FooBaz()
        {
            if(Foo()) Baz();
        }
    
        public void BarBaz()
        {
            if(Bar()) Baz();
        }
    }
    
    public class IntStuff : BaseStuff
    {
        private int input;
        public IntStuff(int input)
        {
            this.input = input;
        }
    
        public bool Foo()
        {
            //logic using input for example
            return input > 0;
        }
    
        //implement Bar and Baz using input
    }
    
    public class StringStuff : BaseStuff
    {
        private string input;
        public IntStuff(string input)
        {
            this.input = input;
        }
    
        //Implement Foo, Bar and Baz
    }
    

    然后在某个地方有一些G方法:

    public void G(BaseStuff stuff1)
    {
        stuff1.FooBaz();
    }
    
    public void G(BaseStuff stuff1, BaseStuff stuff2)
    {
        stuff1.FooBaz();
        stuff2.BarBaz();
    }
    

    然后你可以打电话给:

    G(new IntStuff(10), new StringStuff("hello"));
    G(new StringStuff("hello"), new StringStuff("world"));
    
        9
  •  0
  •   Jordão    14 年前

    您可以使用代码生成来解决这个问题。

    Reflection.Emit . 您还可以使用 T4 在Visual Studio中。

    这些类型真的妨碍了这里的发展。您还可以尝试使用动态语言或C 4动态关键字来解决这个问题。

        10
  •  -1
  •   Waleed A.K.    14 年前

    如果您使用的是C 4.0,则可以使用选项参数进行此操作。 或者可以使用对象

    Foo(object o)
    {
      if (o is int){   }
      else if (o is string){   }
    }
    

    或者可以使用泛型方法 Foo<T>(T o){ }