1

I'm playing Space Engineers, which is a game that allows in-game scripting. I'd like to write a script that refills a ship with certain item types.

The original code just has list of item names:

public readonly List<RequiredItem> requiredItemNames = new List<String>{
    "ilder_Component/Construction", 
    "nt/Computer",
    "_Component/Girder",
    "ponent/MetalGrid",
    "Motor",
    "MyObjectBuilder_Component/SteelPlate",
};

But I'd like to retrieve different amounts for different items. I prepared following struct-ish class:

public class RequiredItem {
     public RequiredItem(string pattern, double req) {
         this.pattern = pattern;
         this.amountRequired = req;
     }
     string pattern;
     double amountRequired = 0;
}

I would like to initialize the list without the repetitive new RequiredItem("name", 12345). I somewhat know this syntax from C++, but not from C#. I tried the following:

public readonly List<RequiredItem> requiredItemNames = new List<String>{
    {"ilder_Component/Construction", 300},  // construction comp
    {"nt/Computer",150},
    {"_Component/Girder",100},
    {"ponent/MetalGrid",70},
    {"Motor",150},
    {"MyObjectBuilder_Component/SteelPlate",333}
};

That gives me error:

Error: No oveload for method 'Add' takes 2 arguments

So I suppose it's trying to put the pairs into List.Add instead of my constructor. How can I establish, that I want the items constructed and then put into Add?

1
  • For what it's worth, hiding the constructor call in an extension method or implicit cast (?) may (or may not) save some work, but I think it makes the code harder to read and maintain. Commented Mar 20, 2019 at 0:30

3 Answers 3

5

Collection initializers simply require an Add method with a compatible signature and it doesn't have to exist on the type itself. (The collection must implement IEnumerable to "prove" it's a collection, though.)

Add an extension method...

public static class RequiredItemListExtensions
{
    public static void Add(this List<RequiredItem> list, string pattern, double req)
    {
        list.Add(new RequiredItem(pattern, req));
    }
}

Then you can initialize it the way you want.

public readonly List<RequiredItem> requiredItemNames = new List<RequiredItem>
{
    {"ilder_Component/Construction", 300},
    {"nt/Computer",150},
    {"_Component/Girder",100},
    {"ponent/MetalGrid",70},
    {"Motor",150},
    {"MyObjectBuilder_Component/SteelPlate",333}
};

As I recall, while implementing Roslyn, Microsoft was able to remove the check that prevented method resolution from selecting an extension method. It's a Roslyn-specific change, not a version-specific change. In my test, even when I drop the LangVersion property down to 3.0 (where collection initializers were first introduced but extension methods weren't selected yet), it still works, whereas a pre-Roslyn compiler would still yield the error in the question.

Update: Confirmed. I found a VS 2013 installation, tried the same code with version 5.0 explicitly selected, and it produced the same error. I loaded the same project in VS 2015, version 5.0 still selected, and it compiled. Space Engineers will need to use a Roslyn-based C# compiler for my answer to work.

Sign up to request clarification or add additional context in comments.

Comments

5

As an alternative to Michael's answer, and to avoid having a Builder method, you could also rely on using the C# implicit keyword, for example:

public class RequiredItem
{
    //snip

    public static implicit operator RequiredItem((string pattern, double req) ri)
    {
        return new RequiredItem(ri.pattern, ri.req);
    }
}

And now you can create your list using Tuples:

public readonly List<RequiredItem> requiredItemNames = new List<RequiredItem>
{
    ("ilder_Component/Construction", 300),  // construction comp
    ("nt/Computer",150),
    ("_Component/Girder",100),
    ("ponent/MetalGrid",70),
    ("Motor",150),
    ("MyObjectBuilder_Component/SteelPlate",333)
};

3 Comments

This seems more succinct, upvote. Also i have 18 more upvotes to use for the day
@MichaelRandall Well they're both as good as each other I think, though personally I would have stuck with repeating new RequiredItem(....)
The curly braces { } are not required.
3

You can't, Object and Collection Initializers don't have a short-hand syntax to construct a list of object like this. You will need to specify the type.

However, you could do something nonsensical like this using Named Tuples

public static List<RequiredItem> Builder(params (string pattern, double req)[] array)
   => array.Select(x => new RequiredItem(x.pattern, x.req)).ToList();


public readonly List<RequiredItem> requiredItemNames = Builder(
   ("ilder_Component/Construction", 300), // construction comp
   ("nt/Computer", 150),
   ("_Component/Girder", 100),
   ("ponent/MetalGrid", 70),
   ("Motor", 150),
   ("MyObjectBuilder_Component/SteelPlate", 333));

Additional Resources

C# tuple types

C# tuples are types that you define using a lightweight syntax. The advantages include a simpler syntax, rules for conversions based on number (referred to as cardinality) and types of elements, and consistent rules for copies, equality tests, and assignments. As a tradeoff, tuples do not support some of the object-oriented idioms associated with inheritance.

Object and Collection Initializers (C# Programming Guide)

C# lets you instantiate an object or collection and perform member assignments in a single statement.

1 Comment

I'm not sure I'd call it nonsensical (it would be a useful function if the list was being created dynamically), but agree that in terms of 'readability', it is simpler to just initialise the list using 'new RequiredItem(string,double) in the array initializer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.