【问题标题】:C# Extension methods on "members"“成员”上的 C# 扩展方法
【发布时间】:2010-01-15 11:49:35
【问题描述】:

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

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() 似乎具有误导性。我在这里不谈论静态属性。我知道这种语法是不可能的。我只是试图用伪语言显示,什么信息是必要的。这将是理想的,每一个额外的东西都只是语法开销。任何接近此的有效语法都会很棒。

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

【问题讨论】:

  • 你到底想达到什么目的?
  • 它是对类型中成员名称的静态反射
  • 您不能在 C#3 中创建静态扩展方法。如果是你自己扩展的类型,为什么不能直接添加这个方法呢?
  • @Quick:像“GetDisplayName”这样的扩展方法适用于任何类的任何成员。 @saret:嗯? @Bruno:我想要一种简单且编译时安全的语法来获取有关成员的信息,例如显示名称。
  • 我想我被误解了。我不想写某个扩展方法。我想要一个简单、可读且编译时安全的语法,使用任何语言功能。

标签: c# lambda extension-methods


【解决方案1】:

查看Lokad Shared Libraries 中的ExpressReflect 类。认为他们可能会帮助您完成您正在尝试做的事情。在这里阅读更多:

【讨论】:

  • 谢谢,很有趣。我可能会在那里找到一些东西。
【解决方案2】:

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

这是一个经常被请求的功能,并且已经在 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); }
}

注意属性上的设置器是如何私有的。这可以防止任何人意外设置属性本身而不是设置 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 容器,您也许可以在不直接进行反射的情况下实现其中的一些。

【讨论】:

  • 正常调用静态方法没有更好的办法吗?
  • 如果您发布有关更广泛问题的更多信息,可能会有。例如根据您正在做的事情的规模,代码生成可能会有所帮助,也可能是矫枉过正。
  • 用不同的想法更新了。
  • 谢谢。不幸的是,这不是我想要的。因为它必须适用于任何类,所以它不能是侵入性的。表达式返回的元信息已经存在于某个地方,例如在属性中或其他地方,这无关紧要。我只是想将对我丑陋的静态方法 (BlahBlahUtility.GetDisplayName) 的调用交换为更易读的东西。
  • 你已经知道你不能“仅仅”这样做。 :) 我不明白为什么你需要这个设施。用例是什么?在什么情况下有人会手写代码来获取与属性关联的显示名称?
【解决方案3】:

您似乎正在尝试创建静态扩展方法?

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

代替

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

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

【讨论】:

  • 我不想要静态扩展方法。我想要一个简单的语法,使用任何语言功能。我在问题中添加了一个部分以更好地解释它。
【解决方案4】:

听起来您将层集成得太紧了。通常在这种情况下,我会让表示层决定GetDisplayName() 的实现,而不是使其成为属性本身的扩展。您可以创建一个名为 MyTypeDisplayer 的接口或任何您喜欢的接口,并让它有多个实现,而不是将您限制为单个显示实现。

【讨论】:

  • 不,如果 GetDisplayName 有一个对象是 MyType.Property 的值,那么它没有该属性的 PropertyInfo。它只具有财产的价值。因此它无法获取自定义属性,例如。
  • 它与图层没有任何关系。 DisplayName 只是一个示例。例如,它可以返回从属性中获取的数据。
  • @Stefan,关键是您正在整合数据以及如何将数据使用到一个位置。如果 MyType 已编译并且您在设计时知道所有属性和属性,则可以创建另一个类,该类以您尝试使用它的方式解释该数据。
  • @NickLarsen:是的,这正是我正在做的。我只想使语法更短,然后调用静态方法。我的意思是,BlahBlahHelperClass.GetDisplayName((myClass x) =&gt; x) 目前是可行的解决方案,我只是想让它更短且更具可读性。
【解决方案5】:

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

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

可以执行类似于 string displayName = ((MyType x) => x.Property).GetDisplayName(); 的操作 第一个问题是保证编译器将您的 (x=> x.Property) 视为表达式而不是动作/函数等... 要做到这一点,可能需要这样做:

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

扩展方法必须像这样定义:

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

如果您的成员也是方法,您可能还必须在 Expression&lt;Action&gt;&gt; and Expression&lt;Action&lt;T&gt;&gt; 之上定义扩展方法。

您可以在 Expression 之后添加一个点 - 这是 Compile 方法所在的位置。

附加:

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

【讨论】:

  • 是的,我已经尝试将 lambda 转换为表达式。但这在语法上远非理想,所以我什至没有提到它。那么直接调用静态方法就更好了。
  • 我同意 - 还发现必须将显式表达式定义放入 (can't use var expression = (t => t.Property); 因为编译器可能不会将其视为表达式,但只是一个 Func 定义 - 所以你需要 var 成为 Expression>) 不幸的是,我不确定一种方法可以让语法达到你想要的干净状态 - 也去了静态扩展方法调用路由,因为它看起来更干净... MyExtensionClass.GetDisplayName(x => TypeOfX.StaticMember),但如果您有该类型的实例,仍然可以使用扩展方法
  • 对不起,我的意思是 - 我同意 - 还发现必须将显式表达式定义放在看起来很脏的语法中
  • 我已经在上面编辑了我的答案 - 请参阅底部以获得更多答案
  • 您可以在执行此操作的类中创建一个辅助方法,该方法采用表达式并将为您调用 ExtensionMethodClass.GetDisplayName => GetDisplayName(Mytype x=> x.Member),公共静态字符串 GetDisplayName (表达式){return ExtensionMethodClass.GetDisplayName(表达式);}
【解决方案6】:

如果你接口你的属性,你可以在接口上做扩展:

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

【讨论】:

  • 也许我遗漏了一些东西,但为什么不把 GetText 方法重命名为 GetDisplayName,然后忘记扩展方法呢?
  • 嗯,说得对。我想我是在考虑静态入口点“Extensions.GetDisplayName(o.Property)”时这样做的
  • 我不能也不想影响 MyType。这可以是任何类型。否则就很容易了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-09
相关资源
最近更新 更多