将匿名类型设置为null。

3

我知道不允许将匿名类型设置为null,但是我该如何解决这个问题:

var products = null; //this cant be null, but somehow it must be declared in this outer scope, and not only inside the try-catch scope

    try
     {
         products = (from p in repository.Products
                     select new { p.Product, p.ProductName }).ToList();
     }
     catch (Exception e)
     {  
       return;
     }
Console.WriteLine(products.FirstOrDefault().ProductName)
7个回答

12
我同意其他答案中提到的,您应该考虑重构这段代码或使用一个命名类型而不是匿名类型。
然而,有一种方法可以在匿名类型的变量中得到空引用。很简单。
static List<T> GimmeANullListOf<T>(T t) { return (List<T>)null; }
...
var products = GimmeANullListOf(new { X = 1, Y = "hello" });

这个技巧被称为“示例强制类型转换”,虽然有些奇怪,但是是合法的。

1
但它考虑到了编译器的内部知识,这可能对其他编译器或将来的编译器版本不适用。(当然,_您_可以使之适用于微软C#编译器...)或者...它是否在规范中有所体现?它是否适用于跨程序集运行? - Jordão
3
@Jordão: 我不明白你的意思。它如何考虑编译器内部的知识?这是合法的C#代码,任何符合规范的实现都需要具有此行为。你为什么认为这是实现定义的行为?"would it work across assemblies?" 意味着什么?我不明白你的问题中'work across'的含义。 - Eric Lippert
@Jordão:如果你所说的“跨程序集工作”是指在两个程序集中有两种方法都返回“相同”的匿名类型对象,你能将它们视为相同类型吗?那么答案是否定的。C#规范规定,匿名类型只能在单个编译中统一。 - Eric Lippert
是的,那就是我想表达的意思。感谢您提供的信息,规范确实提到了类型统一,这正是我想知道的。我之前就知道这种技巧,只是不知道它有多正式。为此点赞。 - Jordão
@Jordão:请参考 C# 4 规范的第 7.6.10.6 节。"在同一程序中,指定具有相同名称和编译时类型顺序的属性序列的两个匿名对象初始化器将生成相同匿名类型的实例。" - Eric Lippert
@Eric Lippert:感谢您提供具体的参考。 - Jordão

5
或者使用 new Nullable<bool>() 也可以很好地完成任务。

2

对于这么简单的事情,而且由于你在异常时使用了return,所以你应该能够只在try块中完成所有操作,而不必在外部声明products

try
{
    var products = (from p in repository.Products
                    select new { p.Product, p.ProductName }).ToList();
    Console.WriteLine(products.FirstOrDefault().ProductName);
}
catch (Exception e)
{  
    return;
}

虽然我非常赞同SLaks的观点,但您不应该吞噬异常并这样返回。

是的,确实会。代码已经简化了,我以为这已经足够清楚了...但还是谢谢。 - Buginator

2

这并不是一个简单的问题。你可以选择改变控制流程,将整个代码块放在 try{} 块中(在这种情况下很容易,但我假设这段代码为说明目的而简化),或者声明一个具体类型,例如

class ProductWithName
{
    public int Product;
    public string ProductName;
}

然后使用。
List<ProductWithName> products = null;

并且

select new ProductWithName { Product = p.Product, ProductName = p.ProductName }

哪种选项最好,实际上取决于你的真实代码长什么样。


你说得对,这段代码被大幅简化了,而且这个解决方案是我自己想出来的。然而,我想避免为此创建一个新类。但这样也可以实现。 - Buginator

1

你想要的是在声明范围之外使用匿名类型。

你可以通过反射来实现:

IEnumerable<object> products = null;

// ...

var anon = products.FirstOrDefault();
Console.WriteLine(anon.GetType().GetProperty("ProductName").GetValue(anon, null));

或者是动态的:

IEnumerable<dynamic> products = null;

// ...

var anon = products.FirstOrDefault();
Console.WriteLine(anon.ProductName);

这很酷,虽然我还没有测试过,但它看起来是合法的。但由于性能原因,这在这种情况下不能使用。 - Buginator
dynamic 的性能特征可能会让您感到惊讶。 - Jordão

1

不能为 null 的不是匿名类型,而是 IEnumerable<T>(或 IQueryable),因为您需要对它进行迭代。

您可以使用products ?? Enumerable.Empty<Product>()将 null 替换为空的 IEnumerable<T>

不要这样吞掉异常。在非常特定的情况下,捕获基类 Exception 是一个好主意,而您的情况不是其中之一。


我认为这需要一个专门的Product类,但我想避免使用它。虽然我的代码已经大大简化了,所以我理解你选择了Product类的方法。 - Buginator

0

由于LINQ具有延迟执行功能,因此您可以在父范围内定义查询,并在try catch块中运行它,如下所示:

var productsDB = new List<Func<string>>() { () => "Apples", () => "Pears", () => "Bannanas" };//, () => { throw new NotImplementedException(); } }; // sorry, couldn't think of a better way to make this fail in 2 mins..

var products = (
    from p in productsDB
    select new 
    {
        Name = p()
    } );

try
{
    products.ToList(); // runs the LINQ query
    products.Dump();   // prints the results (LINQPad)
}
catch { "there was an error".Dump(); }

或者另一个选择是元组类,它非常适合这种情况:

var productsDB = new[] { "Apples", "Pears",  "Bannanas" };

List<Tuple<string>> products;

try
{
    products = (
        from p in productsDB
        select Tuple.Create( p ) ).ToList();

    products.Dump();
}
catch { "there was an error".Dump(); }

// Build anonymous type from (read-only) Tuple here if necessary...

编辑:刚意识到我误读了帖子 :p - 这是我的原始帖子:

我认为这只是类型检查器在抱怨,尝试像这样做一些事情。

var employees = (
    from person in db.denormalisedPeople
    where person.Type == "employee"
    select new
    {
        name = employee.FullName,
        areaID = new Nullable<int>(), // placeholder.. set in a future query                
    } ).ToList();

(未经测试的代码,但这种技术对我来说很有效)


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