关于“成员”的C#扩展方法

时间:2010-01-15 11:49:36

标签: c# lambda extension-methods

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

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 个答案:

答案 0 :(得分:2)

查看Lokad Shared Libraries中的ExpressReflect类。认为他们可能会帮助您尝试做什么。在这里阅读更多内容:

答案 1 :(得分:1)

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

这是一个非常频繁要求的功能,已经在C#团队的会议中讨论了大约十年,但从未被优先考虑过高,不能被包括在内。

此博客文章解释了原因:

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

所以现在,你只是要与一个缺失的功能作斗争。也许您可以发布有关更广泛问题的更多信息,看看人们是否可以提出不同的方法。

<强>更新

如果没有关于您的问题的更多信息,这只是猜测。但是如果你有一个代表一个值的属性,但也带有额外的“元”信息,你总是可以将它表示为一个新类型,并使用“注入”步骤来设置所有内容。

以下是这种“元属性”的建议抽象接口:

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是如何私有的。这会阻止任何人意外设置属性本身,而不是设置Value子属性。

因此,这意味着该类必须设置这些属性,因此它们不仅仅是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容器,您可以实现其中的一部分,而无需直接进行反射。

答案 2 :(得分:0)

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

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

而不是

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

如果这是您想要实现的目标,我认为在当前版本的C#中不可能。

答案 3 :(得分:0)

听起来你将图层整合得太紧了。通常在这种情况下,我会让表示层决定GetDisplayName()的实现,而不是使它成为属性本身的扩展。您可以创建一个名为MyTypeDisplayer的界面或任何您想要的界面,让它有多个实现,而不是限制您使用单个显示器实现。

答案 4 :(得分:0)

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

然而,可以像这样定义表达式来获取对静态成员的引用: ((MyType x)=&gt; MyType.Property)

可以做类似 string displayName =((MyType x)=&gt; x.Property).GetDisplayName(); 第一个问题是保证编译器将您的(x =&gt; x.Property)视为Expression而不是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>>之上定义扩展方法。

你可以在表达式之后做一个点 - 这就是编译方法所在的位置。

追加:

我认为在没有类型1实例的情况下对扩展方法的静态调用需要做“反射”以确定成员名称仍然是最干净的语法 - 这样你仍然可以在使用类型的实例时使用扩展方法并回退到静态调用定义=&gt;当{1}没有实例

MyExtensionClass.GetDisplayName(TypeOfX x => TypeOfX.StaticMember OR x.Property/Member)

答案 5 :(得分:0)

如果您对属性进行界面,则可以在界面上进行扩展:

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();
        }
    }
}