Consider the following class
public class PlanetKrypton
{
public static void CallSuperManforHelp(string helpMessage, params object[] kryptonParams)
{
Console.WriteLine(String.Format(helpMessage,kryptonParams));
}
public static void CallSuperManforHelp(string helpMessage ,string sender,string senderZipCode)
{
Console.WriteLine("{0} from {1}. I am {2}", helpMessage, sender, senderZipCode);
}
}
public class ConsoleMan
{
public static void Main(string[] args)
{
string helpMessage = "I have a flat tire";
string sender = "Jerry";
int wrongZipType = 12345;
PlanetKrypton.CallSuperManforHelp(helpMessage, sender, wrongZipType);
PlanetKrypton.CallSuperManforHelp(helpMessage);
}
}
Now, if I have a more strongly typed method signature in the first method, I would have gotten a compile time error for both these method calls.
Are there any "best practices" for using params in method signature ?
Edit:Am making this a community wiki
I rarely see a need for it, myself.
If my function might need a collection of items, I make it take exactly that: ICollection<> or IEnumerable<>, potentially with an overload that takes a single T for that special case.
If the function is more utilitarian in nature (for example, I have a generic multi-field HashCode generating function), where params might seem to fit, I will still provide quite a few overloads for specific cases like 1 arg, 2 args, 3 args ... sometimes to 5 args or 10 args. Then I will add a catch-all with params. I do this because of the array object creation implicit with params.
I would avoid using params[] object. What I would do is create a class that encapsulates the three strings in your second overload:
public class HelpStuff
{
public string Message{get;set;}
public string Help{get;set;}
public string ZipCode{get;set;}
}
Then have two overloads like this:
public static void CallSuperManforHelp(string helpMessage, params string[] kryptonParams)
{
//do work
}
public static void CallSuperManforHelp(HelpStuff helpStuff)
{
//do work
}
Well, the obvious thing is that compile time errors are better than runtime errors. However, a flexible, usable API sometimes has to take precedence. I'd say in general you should only use arrays of Object, which lack compile time type safety and are sometimes inefficient, if you're sure there's no more static way to accomplish what you want.
If you have this method:
public static void CallSuperManforHelp(string helpMessage, params object[] kryptonParams) { ... }
you can cool it with these code:
CallSuperManforHelp("please help");
CallSuperManforHelp("please help", (object[])null);
These calling are equivalent. So if you overloading the "CallSuperManforHelp", you should think about calling convenction of these methods.
It's rarely needed, but useful at times - so I wouldn't say it's a best practice to avoid it. Just try to avoid ambiguity.
String.Format is of course the canonical example, and most cases where I use params, it's to pass to String.Format (e.g. a logging method).
Another example from the framework is DataRowCollection.Add: it's useful to be able to add field values without building an object array first:
DataTable myDataTable;
...
for(...)
{
myDataTable.Rows.Add(col1Value, col2Value, col3Value);
}
I wouldn't mix params/regular overloads in that way - I'd only generally use params to add an override that takes additional (unknown at compile time) parameters.
e.g. A normal method, and then one that takes an additional params:
public static void CallSuperManforHelp(string helpMessage);
public static void CallSuperManforHelp(string helpMessage, params object[] kryptonParams);
This eliminates the ambiguity between the overloads.
If you want the two methods you have defined, then you could simply give them different names to avoid any ambiguity and clarify their usage:
public static void CallSuperManforHelpFormatted(string helpMessage, params object[] kryptonParams)
public static void CallSuperManforHelp(string helpMessage ,string sender,string senderZipCode)
Related
How can I get both these two methods to compile?
public static IEnumerable<string> DoSomething(params string[] args)
{ // do something }
public static IEnumerable<string> DoSomething(this string[] args)
{ // do something }
I get this compile error:
Type 'Extensions' already defines a member called 'DoSomething' with the same parameter types Extensions.cs
So that I can do this:
new string[] { "", "" }.DoSomething();
Extensions.DoSomething("", "");
Without the params method, I have to do this:
Extensions.DoSomething(new string[] { "", "" });
Update: Based on the answer by O. R. Mapper
public static IEnumerable<string> DoSomething(string arg, params string[] args)
{
// args null check is not required
string[] argscopy = new string[args.Length + 1];
argscopy[0] = arg;
Array.Copy(args, 0, argscopy, 1, args.Length);
return argscopy.DoSomething();
}
Update: I like HugoRune's answer now.
You can add an additional parameter to the params version:
public static IEnumerable<string> DoSomething(string firstArg, params string[] moreArgs)
That should be sufficient for the compiler to distinguish it from the string[] extension method.
As suggested by user SLaks, an additional overload without any arguments should be provided in this case, if the situation with an empty params array needs to be supported:
public static IEnumerable<string> DoSomething()
Late answer:
Another option is to just put both methods in different classes. Since you never us the class name when calling the extension method (the one with the this parameter), the extension method can be in any public static class in the same namespace, without any noticeable difference.
// contains static methods to help with strings
public static class StringTools
{
public static IEnumerable<string> DoSomething(params string[] args)
{
// do something
}
}
// contains only extension methods
public static class StringToolsExtensions
{
public static IEnumerable<string> DoSomething(this string[] args)
{
return StringTools.DoSomething(args);
}
}
This way you avoid copying the string array, you do not need an additional overload with no arguments, and I would say it looks cleaner. I would always separate extension methods and other static methods to avoid confusion.
You can give one of the two methods a different name. i.e. DoSomething2
You can just use one method. It's the same method with the same parameter list; clearly they're doing the same thing (since you didn't give them different names as per #1). Just combine them.
You can change the parameter list of one of the methods. i.e. (this string[] args, object unusedParameter)
I am new to C#, but still
public class Logging
{
public static int Write(params object[] items)
{
return Console.Write(items);
}
}
seems just ok, but does not work.
Well it is not obvious that all instances or Write are defined in compile time, but they are.
If i call
Logging.Write("Hello world");
i got string
System.Object[]
as response
There are two problems with your code:
There is no overload of the Console.Write method that does not return void (e.g., int).
There is no overload of the Console.Write method that takes an array of object. The overload matching is the one taking a single object which converts the object to string by invoking the ToString method. The ToString method returns "System.Object[]" for an array of object.
Are you trying to do something like this?
public class Logging
{
public static void Write(params object[] items)
{
Console.WriteLine(string.Join(" ", items));
}
}
Example:
int x = 42;
Logging.Write("The value of x is", x);
Output:
The value of x is 42
i know this is not what your question is about, but i would suggest using a logging library, i love NLog, then there is Log4Net there are more but those are those i know about!
I appears to me as though there is a bug/inconsistency in the C# compiler.
This works fine (first method gets called):
public void SomeMethod(string message, object data);
public void SomeMethod(string message, params object[] data);
// ....
SomeMethod("woohoo", item);
Yet this causes "The call is ambiguous between the following methods" error:
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ....
SomeMethod("woohoo", (T)item);
I could just use the dump the first method entirely, but since this is a very performance sensitive library and the first method will be used about 75% of the time, I would rather not always wrap things in an array and instantiate an iterator to go over a foreach if there is only one item.
Splitting into different named methods would be messy at best IMO.
Thoughts?
EDIT:
I guess Andrew might be onto something.
Full example:
public static class StringStuffDoer
{
public static string ToString<T>(T item1, T item2)
{
return item2.ToString() + item1.ToString();
}
public static string ToString<T>(T item, params T[] items)
{
StringBuilder builder = new StringBuilder();
foreach (T currentItem in items)
{
builder.Append(currentItem.ToString());
}
return item.ToString() + builder.ToString();
}
public static void CallToString()
{
ToString("someString", null); // FAIL
ToString("someString", "another string"); // SUCCESS
ToString("someString", (string)null); // SUCCESS
}
}
I still think it is odd that the cast is needed - the call is not ambiguous. It works if you replace T with string or object or any non-generic type, so why wouldn't it work for the generic? It properly finds the two possible matching methods, so I believe that by the spec, it should pick the one that doesn't use params if possible. Correct me if I'm wrong here.
(NOT SO) FINAL UPDATE:
Sorry for taking you guys on this tyraid, I've apparently been staring at this too long...too much looking at generics and params for one night. The non-generic version throws the ambiguous error as well, I just had the method signature off in my mockup test.
REAL FINAL UPDATE:
Okay, this is why the problem didn't show up in my non-generic test. I was using "object" as the type parameters. SomeMethod(object) and SomeMethod(params object[]) do NOT throw the ambiguous error, I guess "null" automatically gets cast to "object". A bit strange I'd say, but somewhat understandable, maybe.
So, oddly enough, this call DOES work:
SomeMethod<object>("someMessage", null);
It appears to me as though there is a bug/inconsistency in the C# compiler.
There certainly are bugs and inconsistencies in the compiler. You have not found one of them. The compiler is behaving completely correctly and according to spec in all these cases.
I'm doing my best to make sense of this very confusing question. Let me try to break it down into a series of questions.
Why does this succeed and call the first method?
public void SomeMethod(string message, object data);
public void SomeMethod(string message, params object[] data);
// ....
SomeMethod("woohoo", item);
(Presumption: that item is an expression of a compile-time type other than object[].)
Overload resolution must choose between two applicable methods. The second method is only applicable in its expanded form. A method that is applicable only in its expanded form is automatically worse than a method applicable in its normal form. Therefore the remaining better method is chosen.
Why does this fail with an ambiguity error?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", (T)item);
It is impossible to say because you do not say what "T" is. Where does T come from in this example? There are two type parameters named T declared; is this code in the context of one of those methods? Since those are different types both named T it could make a difference. Or is this yet a third type called T?
Since the question does not have enough information to answer it, I'm going to ask a better question on your behalf.
Why does this fail with an ambiguity error?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", "hello");
It doesn't. It succeeds. Type inference chooses "string" for T in both methods. Both generic methods are applicable; the second is applicable in its expanded form, so it loses.
OK, then why does this fail with an ambiguity error?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", null);
It doesn't. It fails with a "cannot infer T" error. There's not enough information here to determine what T is in either case. Since type inference fails to find an candidate method, the candidate set is empty and overload resolution has nothing to choose between.
So this succeeds because... ?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", (string)null);
Type inference infers that both methods are candidates when constructed with "string". Again, the second method is worse because it is applicable only in its expanded form.
What if we take type inference out of the picture? Why is this ambiguous?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod<string>("woohoo", null);
We now have three applicable candidates. The first method, the second method in its normal form, and the second method in its expanded form. The expanded form is discarded because expanded is worse than normal. That leaves two methods in their normal form, one taking string and the other taking string[]. Which is better?
When faced with this choice we always choose the one with the more specific type. If you said
public void M(string s) { ... }
public void M(object s) { ... }
...
M(null);
we'd choose the string version because string is more specific than object. Every string is an object but not every object is a string.
string is not convertible to string[]. string[] is not convertible to string. Neither is more specific than the other. Therefore this is an ambiguity error; there are two "best" candidates.
Then why does this succeed and what does it do?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod<object>("woohoo", null);
Again we have three candidates, not two. We automatically eliminate the expanded form as before, leaving two. Of the two methods in normal form which remain, which is better?
We must determine which is more specific. Every object array is an object, but not every object is an object array. object[] is more specific than object, so we choose to call the version that takes an object[]. We pass a null parameter array, which is almost certainly not what you intended.
This is why it is an extremely poor programming practice to make overloads like you are doing. You introduce potential for your users to run into all kinds of crazy ambiguities when you do this kind of stuff. Please do not design methods like this.
A better way to design this sort of logic is: (Note that I have not actually compiled this code, this is just off the top of my head.)
static string ToString<T>(T t)
{
return t == null ? "" : t.ToString();
}
static string ToString<T>(T t1, T t2)
{
return ToString<T>(t1) + ToString<T>(t2);
}
static string ToString<T>(T t1, T t2, params T[] rest)
{
string firstTwo = ToString<T>(t1, t2);
if (rest == null) return firstTwo;
var sb = new StringBuilder();
sb.Append(firstTwo);
foreach(T t in rest)
sb.Append(ToString<T>(t));
return sb.ToString();
}
Now every case is handled with reasonable semantics and decent efficiency. And for any given call site you can immediately predict precisely which method will be called; there are only three possibilites: one argument, two arguments or more than two arguments. Each is handled unambiguously by a particular method.
Seems to work for me, does the rest of your code look like the following?
class TestThing<T>
{
public void SomeMethod(string message, T data)
{
Console.WriteLine("first");
}
public void SomeMethod(string message, params T[] data)
{
Console.WriteLine("second");
}
}
class Program
{
static void Main(string[] args)
{
var item = new object();
var test_thing = new TestThing<object>();
test_thing.SomeMethod("woohoo", item);
test_thing.SomeMethod("woohoo", item, item);
Console.ReadLine();
}
}
Compiles fine on .NET 3.5 and outputs "first" and then "second" when run. Which version are you using/targeting?
EDIT
The issue from your code above is that the compiler can't tell what type null is. It is equally valid to assume that it is a string or an array of strings, hence why it is ambiguous when just null and not ambiguous when you specifically cast it (i.e. you're telling the compiler how it should be treated).
UPDATE
"The same can be argued for
SomeMethod(string, string) and
SomeMethod (string, params string[]),
yet it works"
Actually, no it doesn't. You get the same ambiguous method problem.
public static string TypedToString(string item1, string item2)
{
return "";
}
public static string TypedToString(string item1, params string[] items)
{
return "";
}
public static void CallToString()
{
TypedToString("someString", null); // FAIL
}
You should change the signatures to be more specific. Eg:
void Foo(object o1);
void Foo(object o1, object o2);
void Foo(object o1, object o2, object o3, params object[] rest);
Note the difference between the last 2. This solves the ambiguity problem (will work for generics too).
Update:
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, T data1, T data2, params T[] data);
Just 2 methods. No ambiguity.
I want to add this tidbit for anyone who comes across this to explain the ambiguity issue with SomeMethod(object) and SomeMethod(params object[]) that was throwing me of. This was dug up from the C# spec for me on the MSDN forums by Figo Fei:
10.6.1.4 Parameter arrays
A parameter declared with a params modifier is a parameter array.
When performing overload resolution, a method with a parameter array may be applicable either in its normal form or in its expanded form (ยง7.4.3.1). The expanded form of a method is available only if the normal form of the method is not applicable and only if a method with the same signature as the expanded form is not already declared in the same type.
When the type of a parameter array is object[], a potential ambiguity arises between the normal form of the method and the expended form for a single object parameter. The reason for the ambiguity is that an object[] is itself implicitly convertible to type object. The ambiguity presents no problem, however, since it can be resolved by inserting a cast if needed.
Extending methods to any instance is really easy:
public static string LeaveJustNumbers(this string text)
{
return Regex.Replace(text, #"[\D]", "");
}
...
string JustNumbers = "A5gfb343j4".LeaveJustNumber();
But what if i want to extend methods to a sealed class like string, to
work like:
string.Format("Hi:{0}","Fraga");
Is there any way to do it?
If you're talking about 'extending' static methods (or replacing existing ones), then as far as I know, no, you can't do it and I'm not sure why you'd want to.
The main point of extension methods is so that the calling style is that of an method call on the instance. It allows for more elegant syntax and method chaining, amongst other things. LINQ without extension methods would be horrendously painful, for example.
You have three options, one of which is extremely horrible:
Make a normal extension method that makes the call on the static method
public static string SomeExtensionMethod(this string name)
{
return string.Format("Hi:{0}", name);
}
Usage:
Console.WriteLine("Mr Smith".SomeExtensionMethod());
Create a static helper class and make the call using that
Console.WriteLine(MyHelperClass.SomeMethod("Mr Smith"));
And the evil one
Create a static helper class with the same name as the type you want to 'extend' (e.g. public class String) ... then duplicate the static target method's signature (Format) and watch everyone cry hot salty tears of confusion when they see a type named "string" that isn't from the "System" namespace and they have to smatter their .cs file with using String=MyCrazyHacks.String and/or explicit namespaces.
I'm not even sure if you could do this to "string" as it's an alias for System.String, so I've changed the example to use the name "String" instead.
namespace MyCrazyHacks
{
public static class String
{
public static System.String Format(
System.String str, params object[] zeParams)
{
// do bad, unspeakable things that confuses everyone
return System.String.Format(....);
}
}
}
Note: please don't do this because you will cause great suffering...
I am working on rewriting my fluent interface for my IoC class library, and when I refactored some code in order to share some common functionality through a base class, I hit upon a snag.
Note: This is something I want to do, not something I have to do. If I have to make do with a different syntax, I will, but if anyone has an idea on how to make my code compile the way I want it, it would be most welcome.
I want some extension methods to be available for a specific base-class, and these methods should be generic, with one generic type, related to an argument to the method, but the methods should also return a specific type related to the particular descendant they're invoked upon.
Better with a code example than the above description methinks.
Here's a simple and complete example of what doesn't work:
using System;
namespace ConsoleApplication16
{
public class ParameterizedRegistrationBase { }
public class ConcreteTypeRegistration : ParameterizedRegistrationBase
{
public void SomethingConcrete() { }
}
public class DelegateRegistration : ParameterizedRegistrationBase
{
public void SomethingDelegated() { }
}
public static class Extensions
{
public static ParameterizedRegistrationBase Parameter<T>(
this ParameterizedRegistrationBase p, string name, T value)
{
return p;
}
}
class Program
{
static void Main(string[] args)
{
ConcreteTypeRegistration ct = new ConcreteTypeRegistration();
ct
.Parameter<int>("age", 20)
.SomethingConcrete(); // <-- this is not available
DelegateRegistration del = new DelegateRegistration();
del
.Parameter<int>("age", 20)
.SomethingDelegated(); // <-- neither is this
}
}
}
If you compile this, you'll get:
'ConsoleApplication16.ParameterizedRegistrationBase' does not contain a definition for 'SomethingConcrete' and no extension method 'SomethingConcrete'...
'ConsoleApplication16.ParameterizedRegistrationBase' does not contain a definition for 'SomethingDelegated' and no extension method 'SomethingDelegated'...
What I want is for the extension method (Parameter<T>) to be able to be invoked on both ConcreteTypeRegistration and DelegateRegistration, and in both cases the return type should match the type the extension was invoked on.
The problem is as follows:
I would like to write:
ct.Parameter<string>("name", "Lasse")
^------^
notice only one generic argument
but also that Parameter<T> returns an object of the same type it was invoked on, which means:
ct.Parameter<string>("name", "Lasse").SomethingConcrete();
^ ^-------+-------^
| |
+---------------------------------------------+
.SomethingConcrete comes from the object in "ct"
which in this case is of type ConcreteTypeRegistration
Is there any way I can trick the compiler into making this leap for me?
If I add two generic type arguments to the Parameter method, type inference forces me to either provide both, or none, which means this:
public static TReg Parameter<TReg, T>(
this TReg p, string name, T value)
where TReg : ParameterizedRegistrationBase
gives me this:
Using the generic method 'ConsoleApplication16.Extensions.Parameter<TReg,T>(TReg, string, T)' requires 2 type arguments
Using the generic method 'ConsoleApplication16.Extensions.Parameter<TReg,T>(TReg, string, T)' requires 2 type arguments
Which is just as bad.
I can easily restructure the classes, or even make the methods non-extension-methods by introducing them into the hierarchy, but my question is if I can avoid having to duplicate the methods for the two descendants, and in some way declare them only once, for the base class.
Let me rephrase that. Is there a way to change the classes in the first code example above, so that the syntax in the Main-method can be kept, without duplicating the methods in question?
The code will have to be compatible with both C# 3.0 and 4.0.
Edit: The reason I'd rather not leave both generic type arguments to inference is that for some services, I want to specify a parameter value for a constructor parameter that is of one type, but pass in a value that is a descendant. For the moment, matching of specified argument values and the correct constructor to call is done using both the name and the type of the argument.
Let me give an example:
ServiceContainerBuilder.Register<ISomeService>(r => r
.From(f => f.ConcreteType<FileService>(ct => ct
.Parameter<Stream>("source", new FileStream(...)))));
^--+---^ ^---+----^
| |
| +- has to be a descendant of Stream
|
+- has to match constructor of FileService
If I leave both to type inference, the parameter type will be FileStream, not Stream.
I wanted to create an extension method that could enumerate over a list of things, and return a list of those things that were of a certain type. It would look like this:
listOfFruits.ThatAre<Banana>().Where(banana => banana.Peel != Color.Black) ...
Sadly, this is not possible. The proposed signature for this extension method would have looked like:
public static IEnumerable<TResult> ThatAre<TSource, TResult>
(this IEnumerable<TSource> source) where TResult : TSource
... and the call to ThatAre<> fails because both type arguments need to be specified, even though TSource may be inferred from the usage.
Following the advice in other answers, I created two functions: one which captures the source, and another which allows callers to express the result:
public static ThatAreWrapper<TSource> That<TSource>
(this IEnumerable<TSource> source)
{
return new ThatAreWrapper<TSource>(source);
}
public class ThatAreWrapper<TSource>
{
private readonly IEnumerable<TSource> SourceCollection;
public ThatAreWrapper(IEnumerable<TSource> source)
{
SourceCollection = source;
}
public IEnumerable<TResult> Are<TResult>() where TResult : TSource
{
foreach (var sourceItem in SourceCollection)
if (sourceItem is TResult) yield return (TResult)sourceItem;
}
}
}
This results in the following calling code:
listOfFruits.That().Are<Banana>().Where(banana => banana.Peel != Color.Black) ...
... which isn't bad.
Notice that because of the generic type constraints, the following code:
listOfFruits.That().Are<Truck>().Where(truck => truck.Horn.IsBroken) ...
will fail to compile at the Are() step, since Trucks are not Fruits. This beats the provided .OfType<> function:
listOfFruits.OfType<Truck>().Where(truck => truck.Horn.IsBroken) ...
This compiles, but always yields zero results and indeed doesn't make any sense to try. It's much nicer to let the compiler help you spot these things.
If you have only two specific types of registration (which seems to be the case in your question), you could simply implement two extension methods:
public static DelegateRegistration Parameter<T>(
this DelegateRegistration p, string name, T value);
public static ConcreteTypeRegistration Parameter<T>(
this ConcreteTypeRegistration p, string name, T value);
Then you wouldn't need to specify the type argument, so the type inference would work in the example you mentioned. Note that you can implement both of the extension methods just by delegation to a single generic extension method with two type parameters (the one in your question).
In general, C# doesn't support anything like o.Foo<int, ?>(..) to infer only the second type parameter (it would be nice feature - F# has it and it's quite useful :-)). You could probably implement a workaround that would allow you to write this (basically, by separating the call into two method calls, to get two places where the type inferrence can be applied):
FooTrick<int>().Apply(); // where Apply is a generic method
Here is a pseudo-code to demonstrate the structure:
// in the original object
FooImmediateWrapper<T> FooTrick<T>() {
return new FooImmediateWrapper<T> { InvokeOn = this; }
}
// in the FooImmediateWrapper<T> class
(...) Apply<R>(arguments) {
this.InvokeOn.Foo<T, R>(arguments);
}
Why don't you specify zero type parameters? Both can be inferred in your sample. If this is not an acceptable solution for you, I'm frequently encountering this problem too and there's no easy way to solve the problem "infer only one type parameter". So I'll go with the duplicate methods.
What about the following:
Use the definition you provide:
public static TReg Parameter<TReg, T>(
this TReg p, string name, T value)
where TReg : ParameterizedRegistrationBase
Then cast the parameter so the inference engine gets the right type:
ServiceContainerBuilder.Register<ISomeService>(r => r
.From(f => f.ConcreteType<FileService>(ct => ct
.Parameter("source", (Stream)new FileStream(...)))));
I think you need to split the two type parameters between two different expressions; make the explicit one be part of the type of a parameter to the extension method, so inference can then pick it up.
Suppose you declared a wrapper class:
public class TypedValue<TValue>
{
public TypedValue(TValue value)
{
Value = value;
}
public TValue Value { get; private set; }
}
Then your extension method as:
public static class Extensions
{
public static TReg Parameter<TValue, TReg>(
this TReg p, string name, TypedValue<TValue> value)
where TReg : ParameterizedRegistrationBase
{
// can get at value.Value
return p;
}
}
Plus a simpler overload (the above could in fact call this one):
public static class Extensions
{
public static TReg Parameter<TValue, TReg>(
this TReg p, string name, TValue value)
where TReg : ParameterizedRegistrationBase
{
return p;
}
}
Now in the simple case where you are happy to infer the parameter value type:
ct.Parameter("name", "Lasse")
But in the case where you need to explicitly state the type, you can do so:
ct.Parameter("list", new TypedValue<IEnumerable<int>>(new List<int>()))
Looks ugly, but hopefully rarer than the simple fully-inferred kind.
Note that you could just have the no-wrapper overload and write:
ct.Parameter("list", (IEnumerable<int>)(new List<int>()))
But that of course has the disadvantage of failing at runtime if you get something wrong. Unfortunately away from my C# compiler right now, so apologies if this is way off.
I would used the solution:
public class JsonDictionary
{
public static readonly Key<int> Foo = new Key<int> { Name = "FOO" };
public static readonly Key<string> Bar = new Key<string> { Name = "BAR" };
IDictionary<string, object> _data;
public JsonDictionary()
{
_data = new Dictionary<string, object>();
}
public void Set<T>(Key<T> key, T obj)
{
_data[key.Name] = obj;
}
public T Get<T>(Key<T> key)
{
return (T)_data[key.Name];
}
public class Key<T>
{
public string Name { get; init; }
}
}
See:
C#: Exposing type safe API over heterogeneous dictionary