代码之家  ›  专栏  ›  技术社区  ›  Stefan Steinegger

“成员”的扩展方法

  •  6
  • Stefan Steinegger  · 技术社区  · 15 年前

    我有一些扩展方法可以这样使用:

    MyType myObject; 
    string displayName = myObject.GetDisplayName(x => x.Property);
    

    这里的问题是它需要一个实例,即使扩展方法只需要类型 MyType . 因此,如果没有实例,就需要这样调用它:

    string displayName = BlahBlahUtility.GetDisplayName((MyTpe x) => x.Property);
    

    这已经不太好了。

    有没有一种方法可以为这种情况编写更好的语法?

    我真正想做的是(伪语言):

    string displayName = MyType.Property.GetDisplayName()
    

    这当然不适用于C。

    但是像这样的事情呢:

    string displayName = ((MyType x) => x.Property).GetDisplayName();
    

    这也是不可能的(在lambda之后,不接受点)。

    有什么想法吗?


    编辑 :

    我的“最喜欢的语法” MyType.Property.GetDisplayName() 似乎有误导性。这里我不讨论静态属性。我知道这种语法是不可能的。我只是试着用伪语言来说明什么信息是必要的。这是理想的,每一个额外的东西都只是语法开销。任何与此接近的工作语法都是很好的。

    我不想写某种扩展方法。我想要一个简单、易读、编译时安全的语法, 使用任何语言功能 .

    6 回复  |  直到 15 年前
        1
  •  2
  •   Svish    15 年前

    看看 Express Reflect 中的类 Lokad Shared Libraries . 认为他们可以帮助你做你想做的事。在这里阅读更多:

        2
  •  1
  •   Daniel Earwicker    15 年前

    从您的评论:“我想要一个简单的、编译时安全的语法来获取关于成员的信息”。

    这是一个非常经常被要求的功能,在C小组的会议上讨论了大约十年,但从来没有被优先考虑的足够高。

    这篇博文解释了为什么:

    http://blogs.msdn.com/ericlippert/archive/2009/05/21/in-foof-we-trust-a-dialogue.aspx

    所以现在,你只是在和一个缺失的特性作斗争。也许你可以发布更多关于你更广泛问题的信息,看看人们是否可以建议不同的方法。

    更新

    如果没有关于你问题的更多信息,这只是猜测。但是,如果您有一个表示一个值的属性,而且还带有附加的“meta”信息,那么您可以将其表示为一个新类型,并使用“injection”步骤来设置所有内容。

    下面是这样一个“元属性”的抽象接口建议:

    public interface IMetaProperty<TValue>
    {
        TValue Value { get; set; }
    
        string DisplayName { get; }
    
        event Action<TValue, TValue> ValueChanged;
    }
    

    属性值只是另一个子属性,其类型由用户定义。

    我已经输入了显示名称,还有一个额外的好处,就是当值改变时会触发一个事件(所以你可以免费获得“可观测性”)。

    要在类中具有类似于此的属性,您可以这样声明它:

    public class SomeClass
    {
        public IMetaProperty<string> FirstName { get; private set; }
        public IMetaProperty<string> LastName { get; private set; }
        public IMetaProperty<int> Age { get; private set; }
    
        public SomeClass() { MetaProperty.Inject(this); }
    }
    

    注意属性上的setter是如何私有的。这样可以防止任何人意外地设置属性本身,而不是设置值子属性。

    这意味着类必须设置这些属性,这样它们就不仅仅是 null . 它是通过召唤魔法来实现的 Inject 方法,可用于任何类:

    public static class MetaProperty
    {
        // Make it convenient for us to fill in the meta information
        private interface IMetaPropertyInit
        {
            string DisplayName { get; set; }
        }
    
        // Implementation of a meta-property
        private class MetaPropertyImpl<TValue> : IMetaProperty<TValue>, 
                                                 IMetaPropertyInit
        {
            private TValue _value;
    
            public TValue Value
            {
                get { return _value; }
                set
                {
                    var old = _value;
                    _value = value;
                    ValueChanged(old, _value);
                }
            }
    
            public string DisplayName { get; set; }
    
            public event Action<TValue, TValue> ValueChanged = delegate { };
        }
    
        public static void Inject(object target)
        {
            // for each meta property...
            foreach (var property in target.GetType().GetProperties()
                .Where(p => p.PropertyType.IsGenericType && 
                            p.PropertyType.GetGenericTypeDefinition() 
                                == typeof(IMetaProperty<>)))
            {
                // construct an implementation with the correct type
                var impl = (IMetaPropertyInit) 
                    typeof (MetaPropertyImpl<>).MakeGenericType(
                        property.PropertyType.GetGenericArguments()
                    ).GetConstructor(Type.EmptyTypes).Invoke(null);
    
                // initialize any meta info (could examine attributes...)
                impl.DisplayName = property.Name;
    
                // set the value
                property.SetValue(target, impl, null);
            }
        }
    }
    

    它只是使用反射来查找 IMetaProperty 槽隐藏在对象中,并用一个实现填充它们。

    所以现在的用户 SomeClass 可以说:

    var sc = new SomeClass
                 {
                     FirstName = { Value = "Homer" },
                     LastName = { Value = "Simpson" },
                     Age = { Value = 38 },
                 };
    
    Console.WriteLine(sc.FirstName.DisplayName + " = " + sc.FirstName.Value);
    
    sc.Age.ValueChanged += (from, to) => 
        Console.WriteLine("Age changed from " + from + " to " + to);
    
    sc.Age.Value = 39;
    
    // sc.Age = null; compiler would stop this
    

    如果您已经在使用IOC容器,那么您可以在不直接进行反射的情况下实现其中的一些功能。

        3
  •  0
  •   Doctor Blue    15 年前

    看起来您正在尝试创建一个静态扩展方法?

    DateTime yesterday = DateTime.Yesterday(); // Static extension.
    

    而不是

    DateTime yesterday = DateTime.Now.Yesterday(); // Extension on DateTime instance.
    

    如果这是你想要实现的,我不相信在当前版本的C中是可能的。

        4
  •  0
  •   Nick Larsen    15 年前

    听起来您对层的集成有点太紧了。通常在这种情况下,我会让表示层决定 GetDisplayName() 而不是使其成为属性本身的扩展。您可以创建一个名为 MyTypeDisplayer 或者随便你想做什么,让它有多个实现,而不局限于一个显示实现。

        5
  •  0
  •   saret    15 年前

    这里的问题是无法通过实例mytype.[成员]获取对非静态方法的引用。这些只能通过对类型的实例的引用来查看。您也不能在类型声明的顶部构建扩展方法,只能在类型的实例上构建扩展方法-即扩展方法本身必须使用类型的实例(此T X)进行定义。

    但是,可以这样定义表达式以获取对静态成员的引用: (mytype x)=>mytype.property)

    一个人可以做类似的事情 string displayname=(mytype x)=>x.property).getdisplayname(); 第一个问题是保证编译器将(x=>x.property)视为表达式,而不是action/func等… 要做到这一点,可能需要做到:

    string displayName = ((Expression<Func<PropertyType>>)((MyType x) => x.Property).GetDisplayName();
    

    然后必须这样定义扩展方法:

    public static string GetDisplayName<T>(this Expression<Func<T>> expression)
    

    您可能还需要在 Expression<Action>> and Expression<Action<T>> 如果你的成员也是方法。

    您可以在表达式后面做一个点-这是编译方法将驻留的地方。

    附加:

    我认为对扩展方法的静态调用在没有需要对其执行“反射”以确定成员名称的类型实例的情况下仍然是最干净的语法-这样,在使用类型实例时仍然可以使用扩展方法,并返回到静态调用定义=> MyExtensionClass.GetDisplayName(TypeOfX x => TypeOfX.StaticMember OR x.Property/Member) 当没有实例时

        6
  •  0
  •   Dynami Le Savard    15 年前

    如果将属性接口化,则可以改为在接口上进行扩展:

    namespace Linq1
    {
        class Program
        {
            static void Main(string[] args)
            {
                MyType o = new MyType();
                o.Property.GetDisplayName();
            }
        }
    
        public class MyType
        {
            public IDisplayableProperty Property { get; set; }
        }
    
        public interface IDisplayableProperty 
        {
            string GetText();
        }
    
        public class MyProperty1 : IDisplayableProperty 
        {
            public string GetText() { return "MyProperty2"; }
        }
    
        public class MyProperty2 : IDisplayableProperty 
        {
            public string GetText() { return "MyProperty2"; }
        }
    
        public static class Extensions
        {
            public static string GetDisplayName(this IDisplayableProperty o)
            {
                return o.GetText();
            }
        }
    }