C#中的钻石语法

20

Java 7现在有了“钻石语法”,我可以这样做:ArrayList<int> = new ArrayList<>();

我想知道C#是否有类似的语法可以利用。
例如,我有类的以下部分:

class MyClass
{
    public List<double[][]> Prototypes; // each prototype is a array of array of doubles

    public MyClass()
    {
        Prototypes = new List<double[][]>; // I'd rather do List<>, in case I change the representation of a prototype later
    }
}

有人知道这是否可行,如果可行的话,我该怎么使用它呢?


3
顺便提一下,请勿使用“数组的数组”。相反,请创建适当的数据模型。 - Federico Berasategui
4个回答

14

不,C# 中没有类似于 diamond syntax 的语法。最接近的方式是这样的:

public static class Lists
{
    public static List<T> NewList<T>(List<T> ignored)
    {
        return new List<T>();
    }
}

然后:

public MyClass()
{
    ProtoTypes = Lists.NewList(ProtoTypes);
}

这只是使用普通的方法类型推论来获取T。请注意,参数的实际值完全被忽略 - 只有编译时类型才是重要的。

个人认为这非常丑陋,我会直接使用构造函数。如果更改了ProtoTypes的类型,编译器将会察觉到区别,修复起来也不会花费太多时间...

编辑:有两种替代方案可供考虑:

  • A similar method, but with an out parameter:

    public static class Lists
    {
        public static void NewList<T>(out List<T> list)
        {
            list = new List<T>();
        }
    }
    
    ...
    
    Lists.NewList(out ProtoTypes);
    
  • The same method, but as an extension method, with the name New:

    public static class Lists
    {
        public static List<T> New<T>(this List<T> list)
        {
            return new List<T>();
        }
    }
    
    ...
    
    ProtoTypes = ProtoTypes.New();
    
我更喜欢第一种方法,而不是这两种方法中的任何一种 :)

9
在设计 C# 3 的过程中,我们考虑了形如 List<?> mylist = new List<int>() 的“嗓音类型”(mumble typing) ,因为它使得涉及匿名类型的某些场景更易于编写。显然,这种方法最终并没有被实现。 - Eric Lippert
8
很明显,解决方案是在 List<T> 中重载一元运算符 + ,使其返回一个新的 List<T>。然后构造函数体只需这样写:ProtoTypes = +ProtoTypes; 毕竟,对于无法用字面量表示的类型来说,一元加运算符没有其他重要的用途,对吧? ;) - Jon Skeet
1
@HighCore:是针对静态方法还是运算符的可怕性版本? - Jon Skeet
@JonSkeet 这个静态方法也可以转换成扩展方法,但是操作符重载是我从未想象过的。 - Federico Berasategui
2
@HighCore:我最初考虑的是扩展方法,事实上 - 我已经添加了一个out参数变体。可惜我们不能单独添加运算符,否则我们可以使用运算符。然后尖叫着逃跑。 - Jon Skeet

7
正如Jon Skeet所说,以及Eric Lippert所证实的那样,在C#中的泛型类构造函数不能从参数或分配给构造函数的变量类型中推断出其类型。当这种类型的行为非常有用时,常用的模式通常是使用静态的泛型工厂方法,该方法可以从其参数的类型中推断出自己的泛型类型。Tuple.Create()是一个例子; 给它任何最多8个参数的列表,它将创建一个具有这些参数作为数据字段的强类型泛型Tuple。然而,这对于您的情况不太适用。
当变量将是局部变量时,考虑采用另一种方式; 通过var关键字使用变量类型推断:
var Prototypes = new List<double[][]>();

这是C#团队在实例化变量时决定减少输入的方式。局部变量比实例变量更频繁地创建和更改,这种方法使得C#代码看起来有点像JavaScript。
正如Jon所展示的那样,隐藏混乱是可能的,但在此过程中会创建更多混乱。以下是另一种可能性,使用.NET 3.5/4.0的Expression功能:
public static string GetName(this Expression<Func<object>> expr)
{
    if (expr.Body.NodeType == ExpressionType.MemberAccess)
        return ((MemberExpression) expr.Body).Member.Name;

    //most value type lambdas will need this because creating the Expression
    //from the lambda adds a conversion step.
    if (expr.Body.NodeType == ExpressionType.Convert
            && ((UnaryExpression)expr.Body).Operand.NodeType 
                == ExpressionType.MemberAccess)
        return ((MemberExpression)((UnaryExpression)expr.Body).Operand)
                   .Member.Name;

    throw new ArgumentException(
        "Argument 'expr' must be of the form ()=>variableName.");
}

public static void InitializeNew(this object me, params Expression<Func<T>>[] exprs) 
    where T:new()
{
    var myType = me.GetType();
    foreach(var expr in exprs)
    {
       var memberName = expr.GetName()
       var myMember = myType.GetMember(memberName,
               BindingFlags.Instance|BindingFlags.Public
                   |BindingFlags.NonPublic|BindingFlags.FlattenHierarchy,
               MemberTypes.Field|MemberTypes.Property);

       if(myMember == null) 
           throw new InvalidOperationException(
               "Only property or field members are valid as expression parameters");

       //it'd be nice to put these under some umbrella of "DataMembers",
       //abstracting the GetValue/SetValue methods
       if(myMember.MemberType == MemberTypes.Field)
           ((FieldInfo)myMember).SetValue(me, new T());
       else
           ((PropertyInfo)myMember).SetValue(me, new T());
    }
}

//usage
class MyClass
{
    public List<double[][]> list1;
    public List<double[][]> list2;
    public MyOtherObject object1;

    public MyClass()
    {
       this.Initialize(()=>list1, ()=>list2);
       this.Initialize(()=>object1); //each call can only have parameters of one type
    }
}

这里的含义很明显,它会带来更多的麻烦,并且价值不大。
为了解释我为什么似乎只是把这个东西放在那里,上面的内容是我用来根据传递的参数抛出ArgumentNullExceptions的方法的改编版本,它需要将值封装在表达式中以保留调用方法的实际参数的名称。在那种情况下,后台的复杂性得到了减少,因为我在主助手中所需的仅是空检查,而增加的复杂性可以为我节省更多时间,通过允许我在代码库的每个方法和构造函数中单行执行空检查。
我建议使用ReSharper作为长期减少打字量的解决方案。当赋值对象的类型已知(例如字段和属性),并且您键入= new时,ReSharper会弹出构造函数类型的建议,并在您想要的情况下自动填充它。如果之后更改了类型或构造函数,则R#将标记该赋值不一致,并且您可以告诉R#更改任何一个以匹配另一个。

1
在IDEA / Android Studio中内置了很多东西,而这些东西在Visual Studio的扩展程序中才存在。 - nasch

3

如果你只想减少代码冗长度,可以使用相反的简略语法: var 操作符。

原始写法:List<int> intList = new List<int>();

新写法:var intList = new List<int>();

至少你只需要写一次 List


不幸的是,如果我们想要一个与封装声明的类型不同的类型,即在属性上,它就无法工作。例如ISet<int> = new HashSet<int>(); - henry700

0
你可以使用new()。例如 List<int> numbers = new(); //将创建新的List<int>()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接