Related
I am writing a simple C#.NET application where I have an if condition where I am checking if a string variable value is a string or another string or another string or another one, etcetc.
Something like this:
if(protocollo == "2019/0002391" || protocollo == "2019/0002390" || protocollo == "2019/0001990" || ........)
This solution works but it is not so elegant. What could be a smarter way to implement the same behavior?
I agree with #JeroenMostert that it really depends on the context of the rest of your application. That said, using an array of strings and checking if your string is in there is a nice straightforward solution. There are certianily solutions that would scale better, take a look at HashSet.
string[] s = new string[] { "2019/0002391", "2019/0002390", "2019/0001990", ... };
if (s.Contains(protocollo)) {
// fill in here
}
You never said, so I'm making the assumption that the strings you're checking against is hard-coded and not something that changes often. To that end, you could create a string[] or HashSet<string> in a static class so it only initializes the one time, then expose a method for checking a second string against the valid ones.
void Main()
{
Console.WriteLine(Protocols.ValidProtocol("2019/0002391")); //True
Console.WriteLine(Protocols.ValidProtocol("2018/0000000")); //False
}
// Define other methods and classes here
public static class Protocols
{
public static bool ValidProtocol(string protocol)
{
return _validProtocols.Contains(protocol);
}
private static readonly HashSet<string> _validProtocols = new HashSet<string>
{
"2019/0002391",
"2019/0002390",
"2019/0001990"
//etc, etc...
};
}
A solution like this would probably not be ideal if the list of strings you need to check against changes often. You'd probably want to pull the list from an external source like a file or a database if you need to modify it often.
I had some code similar to your example in a static extension method. I didn't want to have to instantiate an array every time the method was called, but I wanted to improve the readability of the code.
I improved the code using the switch expression which was added in C# 8. Here is the what your example might look like if implemented with a switch expression. Depending on what your code does if the condition is true you may be able to improve on this, but this is the basics.
var isProtocolloMatch = protocollo switch
{
"2019/0002391" => true,
"2019/0002390" => true,
"2019/0001990" => true,
_ => false
};
if (isProtocolloMatch)
{
// do stuff
}
I know this is a basic question, but I couldn't find an answer.
Why use it? if you write a function or a method that's using it, when you remove it the code will still work perfectly, 100% as without it. E.g:
With params:
static public int addTwoEach(params int[] args)
{
int sum = 0;
foreach (var item in args)
sum += item + 2;
return sum;
}
Without params:
static public int addTwoEach(int[] args)
{
int sum = 0;
foreach (var item in args)
sum += item + 2;
return sum;
}
With params you can call your method like this:
addTwoEach(1, 2, 3, 4, 5);
Without params, you can’t.
Additionally, you can call the method with an array as a parameter in both cases:
addTwoEach(new int[] { 1, 2, 3, 4, 5 });
That is, params allows you to use a shortcut when calling the method.
Unrelated, you can drastically shorten your method:
public static int addTwoEach(params int[] args)
{
return args.Sum() + 2 * args.Length;
}
Using params allows you to call the function with no arguments. Without params:
static public int addTwoEach(int[] args)
{
int sum = 0;
foreach (var item in args)
{
sum += item + 2;
}
return sum;
}
addtwoEach(); // throws an error
Compare with params:
static public int addTwoEach(params int[] args)
{
int sum = 0;
foreach (var item in args)
{
sum += item + 2;
}
return sum;
}
addtwoEach(); // returns 0
Generally, you can use params when the number of arguments can vary from 0 to infinity, and use an array when numbers of arguments vary from 1 to infinity.
It allows you to add as many base type parameters in your call as you like.
addTwoEach(10, 2, 4, 6)
whereas with the second form you have to use an array as parameter
addTwoEach(new int[] {10,2,4,6})
One danger with params Keyword is, if after Calls to the Method have been coded,
someone accidentally / intentionally removes one/more required Parameters from the Method Signature,
and
one/more required Parameters immediately prior to the params Parameter prior to the Signature change were Type-Compatible with the params Parameter,
those Calls will continue to compile with one/more Expressions previously intended for required Parameters being treated as the optional params Parameter. I just ran into the worst possible case of this: the params Parameter was of Type object[].
This is noteworthy because developers are used to the compiler slapping their wrists with the much, much more common scenario where Parameters are removed from a Method with all required Parameters (because the # of Parameters expected would change).
For me, it's not worth the shortcut. (Type)[] without params will work with 0 to infinity # of Parameters without needing Overrides. Worst case is you'll have to add a , new (Type) [] {} to Calls where it doesn't apply.
Btw, imho, the safest (and most readable practice) is to:
pass via Named Parameters (which we can now do even in C# ~2 decades after we could in VB ;P), because:
1.1. it's the only way that guarantees prevention of unintended values passed to Parameters after Parameter order, Compatible-Type and/or count change after Calls have been coded,
1.2. it reduces those chances after a Parameter meaning change, because the likely new identifier name reflecting the new meaning is right next to the value being passed to it,
1.3. it avoids having to count commas and jump back & forth from Call to Signature to see what Expression is being passed for what Parameter, and
1.3.1. By the way, this reason alone should be plenty (in terms of avoiding frequent error-prone violations of the DRY Principle just to read the code not to mention also modify it), but this reason can be exponentially more important if there are one/more Expressions being Passed that themselves contain commas, i.e. Multi-Dimensional Array Refs or Multi-Parameter Function Calls. In that case, you couldn't even use (which even if you could, would still be adding an extra step per Parameter per Method Call) a Find All Occurrences in a Selection feature in your editor to automate the comma-counting for you.
1.4. if you must use Optional Parameters (params or not), it allows you to search for Calls where a particular Optional Parameter is Passed (and therefore, most likely is not or at least has the possibility of being not the Default Value),
(NOTE: Reasons 1.2. and 1.3. can ease and reduce chances of error even on coding the initial Calls not to mention when Calls have to be read and/or changed.))
and
do so ONE - PARAMETER - PER - LINE for better readability (because:
2.1. it's less cluttered, and
2.2. it avoids having to scroll right & back left (and having to do so PER - LINE, since most mortals can't read the left part of multiple lines, scroll right and read the right part)).
2.3. it's consistent with the "Best Practice" we've already evolved into for Assignment Statements, because every Parameter Passed is in essence an Assignment Statement (assigning a Value or Reference to a Local Variable). Just like those who follow the latest "Best Practice" in Coding Style wouldn't dream of coding multiple Assignment Statements per line, we probably shouldn't (and won't once "Best Practice" catches up to my "genius" ;P ) do so when Passing Parameters.
NOTES:
Passing in Variables whose names mirror the Parameters' doesn't help when:
1.1. you're passing in Literal Constants (i.e. a simple 0/1, false/true or null that even "'Best Practices'" may not require you use a Named Constant for and their purpose can't be easily inferred from the Method name),
1.2. the Method is significantly lower-level / more generic than the Caller such that you would not want / be able to name your Variables the same/similar to the Parameters (or vice versa), or
1.3. you're re-ordering / replacing Parameters in the Signature that may result in prior Calls still Compiling because the Types happen to still be compatible.
Having an auto-wrap feature like VS does only eliminates ONE (#2.2) of the 8 reasons I gave above. Prior to as late as VS 2015, it did NOT auto-indent (!?! Really, MS?!?) which increases severity of reason #2.2.
VS should have an option that generates Method Call snippets with Named Parameters (one per line of course ;P) and a compiler option that requires Named Parameters (similar in concept to Option Explicit in VB which, btw, the requirement of was prolly once thought equally as outrageous but now is prolly required by "'Best Practices'"). In fact, "back in my day" ;), in 1991 just months into my career, even before I was using (or had even seen) a language with Named Parameters, I had the anti-sheeple / "just cuz you can, don't mean you should" / don't blindly "cut the ends of the roast" sense enough to simulate it (using in-line comments) without having seen anyone do so. Not having to use Named Parameters (as well other syntax that save "'precious'" source code keystrokes) is a relic of the Punch Card era when most of these syntaxes started. There's no excuse for that with modern hardware and IDE's and much more complex software where readability is much, Much, MUCH more important. "Code is read much more often than is written". As long as you're not duplicating non-auto-updated code, every keystroke saved is likely to cost exponentially more when someone (even yourself) is trying to read it later.
No need to create overload methods, just use one single method with params as shown below
// Call params method with one to four integer constant parameters.
//
int sum0 = addTwoEach();
int sum1 = addTwoEach(1);
int sum2 = addTwoEach(1, 2);
int sum3 = addTwoEach(3, 3, 3);
int sum4 = addTwoEach(2, 2, 2, 2);
params also allows you to call the method with a single argument.
private static int Foo(params int[] args) {
int retVal = 0;
Array.ForEach(args, (i) => retVal += i);
return retVal;
}
i.e. Foo(1); instead of Foo(new int[] { 1 });. Can be useful for shorthand in scenarios where you might need to pass in a single value rather than an entire array. It still is handled the same way in the method, but gives some candy for calling this way.
Adding params keyword itself shows that you can pass multiple number of parameters while calling that method which is not possible without using it. To be more specific:
static public int addTwoEach(params int[] args)
{
int sum = 0;
foreach (var item in args)
{
sum += item + 2;
}
return sum;
}
When you will call above method you can call it by any of the following ways:
addTwoEach()
addTwoEach(1)
addTwoEach(new int[]{ 1, 2, 3, 4 })
But when you will remove params keyword only third way of the above given ways will work fine. For 1st and 2nd case you will get an error.
One more important thing needs to be highlighted. It's better to use params because it is better for performance. When you call a method with params argument and passed to it nothing:
public void ExampleMethod(params string[] args)
{
// do some stuff
}
call:
ExampleMethod();
Then a new versions of the .Net Framework do this (from .Net Framework 4.6):
ExampleMethod(Array.Empty<string>());
This Array.Empty object can be reused by framework later, so there are no needs to do redundant allocations. These allocations will occur when you call this method like this:
ExampleMethod(new string[] {});
Might sound stupid,
But Params doesn't allow multidimensional array.
Whereas you can pass a multidimensional array to a function.
Another example
public IEnumerable<string> Tokenize(params string[] words)
{
...
}
var items = Tokenize(product.Name, product.FullName, product.Xyz)
It enhances code brevity. Why be lengthy when you can be concise?
using System;
namespace testingParams
{
class Program
{
private void lengthy(int[] myArr)
{
foreach (var item in myArr)
{
//...
}
}
private void concise(params int[] myArr) {
foreach (var item in myArr)
{
//...
}
}
static void Main(string[] args)
{
Program p = new Program();
//Why be lengthy...:
int[] myArr = new int[] { 1, 2, 3, 4, 5 };
p.lengthy(myArr);
//When you can be concise...:
p.concise(1, 2, 3, 4, 5);
}
}
}
If you remove the keyword params, the caller code will not work as desired.
I know this is a basic question, but I couldn't find an answer.
Why use it? if you write a function or a method that's using it, when you remove it the code will still work perfectly, 100% as without it. E.g:
With params:
static public int addTwoEach(params int[] args)
{
int sum = 0;
foreach (var item in args)
sum += item + 2;
return sum;
}
Without params:
static public int addTwoEach(int[] args)
{
int sum = 0;
foreach (var item in args)
sum += item + 2;
return sum;
}
With params you can call your method like this:
addTwoEach(1, 2, 3, 4, 5);
Without params, you can’t.
Additionally, you can call the method with an array as a parameter in both cases:
addTwoEach(new int[] { 1, 2, 3, 4, 5 });
That is, params allows you to use a shortcut when calling the method.
Unrelated, you can drastically shorten your method:
public static int addTwoEach(params int[] args)
{
return args.Sum() + 2 * args.Length;
}
Using params allows you to call the function with no arguments. Without params:
static public int addTwoEach(int[] args)
{
int sum = 0;
foreach (var item in args)
{
sum += item + 2;
}
return sum;
}
addtwoEach(); // throws an error
Compare with params:
static public int addTwoEach(params int[] args)
{
int sum = 0;
foreach (var item in args)
{
sum += item + 2;
}
return sum;
}
addtwoEach(); // returns 0
Generally, you can use params when the number of arguments can vary from 0 to infinity, and use an array when numbers of arguments vary from 1 to infinity.
It allows you to add as many base type parameters in your call as you like.
addTwoEach(10, 2, 4, 6)
whereas with the second form you have to use an array as parameter
addTwoEach(new int[] {10,2,4,6})
One danger with params Keyword is, if after Calls to the Method have been coded,
someone accidentally / intentionally removes one/more required Parameters from the Method Signature,
and
one/more required Parameters immediately prior to the params Parameter prior to the Signature change were Type-Compatible with the params Parameter,
those Calls will continue to compile with one/more Expressions previously intended for required Parameters being treated as the optional params Parameter. I just ran into the worst possible case of this: the params Parameter was of Type object[].
This is noteworthy because developers are used to the compiler slapping their wrists with the much, much more common scenario where Parameters are removed from a Method with all required Parameters (because the # of Parameters expected would change).
For me, it's not worth the shortcut. (Type)[] without params will work with 0 to infinity # of Parameters without needing Overrides. Worst case is you'll have to add a , new (Type) [] {} to Calls where it doesn't apply.
Btw, imho, the safest (and most readable practice) is to:
pass via Named Parameters (which we can now do even in C# ~2 decades after we could in VB ;P), because:
1.1. it's the only way that guarantees prevention of unintended values passed to Parameters after Parameter order, Compatible-Type and/or count change after Calls have been coded,
1.2. it reduces those chances after a Parameter meaning change, because the likely new identifier name reflecting the new meaning is right next to the value being passed to it,
1.3. it avoids having to count commas and jump back & forth from Call to Signature to see what Expression is being passed for what Parameter, and
1.3.1. By the way, this reason alone should be plenty (in terms of avoiding frequent error-prone violations of the DRY Principle just to read the code not to mention also modify it), but this reason can be exponentially more important if there are one/more Expressions being Passed that themselves contain commas, i.e. Multi-Dimensional Array Refs or Multi-Parameter Function Calls. In that case, you couldn't even use (which even if you could, would still be adding an extra step per Parameter per Method Call) a Find All Occurrences in a Selection feature in your editor to automate the comma-counting for you.
1.4. if you must use Optional Parameters (params or not), it allows you to search for Calls where a particular Optional Parameter is Passed (and therefore, most likely is not or at least has the possibility of being not the Default Value),
(NOTE: Reasons 1.2. and 1.3. can ease and reduce chances of error even on coding the initial Calls not to mention when Calls have to be read and/or changed.))
and
do so ONE - PARAMETER - PER - LINE for better readability (because:
2.1. it's less cluttered, and
2.2. it avoids having to scroll right & back left (and having to do so PER - LINE, since most mortals can't read the left part of multiple lines, scroll right and read the right part)).
2.3. it's consistent with the "Best Practice" we've already evolved into for Assignment Statements, because every Parameter Passed is in essence an Assignment Statement (assigning a Value or Reference to a Local Variable). Just like those who follow the latest "Best Practice" in Coding Style wouldn't dream of coding multiple Assignment Statements per line, we probably shouldn't (and won't once "Best Practice" catches up to my "genius" ;P ) do so when Passing Parameters.
NOTES:
Passing in Variables whose names mirror the Parameters' doesn't help when:
1.1. you're passing in Literal Constants (i.e. a simple 0/1, false/true or null that even "'Best Practices'" may not require you use a Named Constant for and their purpose can't be easily inferred from the Method name),
1.2. the Method is significantly lower-level / more generic than the Caller such that you would not want / be able to name your Variables the same/similar to the Parameters (or vice versa), or
1.3. you're re-ordering / replacing Parameters in the Signature that may result in prior Calls still Compiling because the Types happen to still be compatible.
Having an auto-wrap feature like VS does only eliminates ONE (#2.2) of the 8 reasons I gave above. Prior to as late as VS 2015, it did NOT auto-indent (!?! Really, MS?!?) which increases severity of reason #2.2.
VS should have an option that generates Method Call snippets with Named Parameters (one per line of course ;P) and a compiler option that requires Named Parameters (similar in concept to Option Explicit in VB which, btw, the requirement of was prolly once thought equally as outrageous but now is prolly required by "'Best Practices'"). In fact, "back in my day" ;), in 1991 just months into my career, even before I was using (or had even seen) a language with Named Parameters, I had the anti-sheeple / "just cuz you can, don't mean you should" / don't blindly "cut the ends of the roast" sense enough to simulate it (using in-line comments) without having seen anyone do so. Not having to use Named Parameters (as well other syntax that save "'precious'" source code keystrokes) is a relic of the Punch Card era when most of these syntaxes started. There's no excuse for that with modern hardware and IDE's and much more complex software where readability is much, Much, MUCH more important. "Code is read much more often than is written". As long as you're not duplicating non-auto-updated code, every keystroke saved is likely to cost exponentially more when someone (even yourself) is trying to read it later.
No need to create overload methods, just use one single method with params as shown below
// Call params method with one to four integer constant parameters.
//
int sum0 = addTwoEach();
int sum1 = addTwoEach(1);
int sum2 = addTwoEach(1, 2);
int sum3 = addTwoEach(3, 3, 3);
int sum4 = addTwoEach(2, 2, 2, 2);
params also allows you to call the method with a single argument.
private static int Foo(params int[] args) {
int retVal = 0;
Array.ForEach(args, (i) => retVal += i);
return retVal;
}
i.e. Foo(1); instead of Foo(new int[] { 1 });. Can be useful for shorthand in scenarios where you might need to pass in a single value rather than an entire array. It still is handled the same way in the method, but gives some candy for calling this way.
Adding params keyword itself shows that you can pass multiple number of parameters while calling that method which is not possible without using it. To be more specific:
static public int addTwoEach(params int[] args)
{
int sum = 0;
foreach (var item in args)
{
sum += item + 2;
}
return sum;
}
When you will call above method you can call it by any of the following ways:
addTwoEach()
addTwoEach(1)
addTwoEach(new int[]{ 1, 2, 3, 4 })
But when you will remove params keyword only third way of the above given ways will work fine. For 1st and 2nd case you will get an error.
One more important thing needs to be highlighted. It's better to use params because it is better for performance. When you call a method with params argument and passed to it nothing:
public void ExampleMethod(params string[] args)
{
// do some stuff
}
call:
ExampleMethod();
Then a new versions of the .Net Framework do this (from .Net Framework 4.6):
ExampleMethod(Array.Empty<string>());
This Array.Empty object can be reused by framework later, so there are no needs to do redundant allocations. These allocations will occur when you call this method like this:
ExampleMethod(new string[] {});
Might sound stupid,
But Params doesn't allow multidimensional array.
Whereas you can pass a multidimensional array to a function.
Another example
public IEnumerable<string> Tokenize(params string[] words)
{
...
}
var items = Tokenize(product.Name, product.FullName, product.Xyz)
It enhances code brevity. Why be lengthy when you can be concise?
using System;
namespace testingParams
{
class Program
{
private void lengthy(int[] myArr)
{
foreach (var item in myArr)
{
//...
}
}
private void concise(params int[] myArr) {
foreach (var item in myArr)
{
//...
}
}
static void Main(string[] args)
{
Program p = new Program();
//Why be lengthy...:
int[] myArr = new int[] { 1, 2, 3, 4, 5 };
p.lengthy(myArr);
//When you can be concise...:
p.concise(1, 2, 3, 4, 5);
}
}
}
If you remove the keyword params, the caller code will not work as desired.
Is it possible to know the length of a string array - without having an object instance - via reflection?
E.g. in this case: 2.
public string[] Key
{
get { return new string[] { Name, Type }; }
}
EDIT: ok, I will not try to do this, it doesn't make much sense.
Perhaps you mean "without having the exact type of the Array". C# Arrays all derive from Array, so you can cast an Array reference to Array and use the Length property.
If you TRULY wants to reflect the property,
var type = typeof(MyClass);
var prop = type.GetProperty("Key");
var method = prop.GetGetMethod();
var body = method.GetMethodBody();
var ils = body.GetILAsByteArray();
from here you'll have to use one of the various libraries to decode bytes to IL OpCodes (for example https://gist.github.com/104001) . The OpCode you are looking for is newarr. The last push of an int32 before the newarr is the size of the array.
You have two things going on there... telling the length of an array is pretty simple once you have an array; you just call .Length (in the case of a vector).
However, you mention an instance, and you are showing an instance property; which makes me think it is the containing object you lack. In which case... no. You can't make a virtcall on a null instance. And trying to use static-call on an instance member of a class is very evil; IIRC the runtime will kick you for this.
You could, however, make it a static property just by adding the static modifier. Then you just pass in null as the instance to reflection.
I guess you mean you want to know the size of the array the property will return if it were called?
I don't think you can do it sensibly.
If the property had a conditional then it could return different sized arrays, so
you'd have to evaluate the property to know the size. Which could have side effects or be dependent on other values in the object (or statics).
Consider this one:-
static public int n;
public string[] Key
{
get {
if (n > 1)
return new string[] { "Name", "Type" };
else
return new string[] { "Name", "Type", "Cheese" };
}
}
Basically, you'd have to run the code.
What are the pro/cons of using the params keyword vs. a List as input to some c# function?
Mainly what are the performance considerations and other trade offs.
The params keyword is syntactic sugar handled by the C# compiler. underneath the hood, it's actually turning
void Foo(params object[] a) { ... }
Foo(1,2,"THREE");
into
void Foo(object[] a) { ... }
Foo(new object[] { 1, 2, "THREE" })
From a performance perspective like you're asking about, the params call is simply faster because it is a bit faster to create an array than it is to create a List<>. There is no performance difference between the two snippets above.
Personally I use params when writing functions that take a number of inputs provided by another programmer (for example String.Format), and IEnumerable when writing functions that take a list of data items provided by the computer (for example File.Write).
The performance implications are negligible. Worrying about the performance of a trivial thing like this is exactly what Donald Knuth was talking about in the famous "premature optimization is the root of all evil" quote.
That said, the asker seems to be fixated on it, so here you go:
Results for 10 million iterations:
params took 308 ms
list took 879 ms
From these results we can see that the params array is just over twice as fast. The simple fact that you can call either of these things Ten Million Times in under a second means that you're completely wasting your time by worrying about it. Use whatever suits your code the best.
The code to test it (compiled and run in release mode using VS2008)
class Program
{
const int COUNT = 10000000;
static IEnumerable<string> m_value = null;
static void ParamsMethod(params string[] args)
{ m_value = args; } // do something with it to stop the compiler just optimizing this method away
static void ListMethod(List<string> args)
{ m_value = args; } // do SOMETHING with it to stop the compiler just optimizing this method away
static void Main(string[] args)
{
var s = new Stopwatch();
s.Start();
for (int i = 0; i < COUNT; ++i)
ParamsMethod("a", "b", "c");
Console.WriteLine("params took {0} ms", s.ElapsedMilliseconds);
s.Reset();
s.Start();
for (int i = 0; i < COUNT; ++i)
ListMethod(new List<string> { "a", "b", "c" });
Console.WriteLine("list took {0} ms", s.ElapsedMilliseconds);
}
}
Well, with the params keyword you could enter arguments into a method like this:
MethodName(1, 2, 3, 4);
But with a list, you'd do it like this:
MethodName(new List<int> {1, 2, 3, 4});
The syntax can be a bit more clear in the former than the latter. This is useful when you just have one parameter to pass in:
// params
MethodName(1);
// List
MethodName(new List<int> {1});
Well, the params allows for a nicer syntax when calling it, but the list (assuming you mean IList<>) is more flexible because different classes may implement the interface. Passing a List<> only makes sense if you need to perform specific operations on the list which are not supported by the interface (such as ToArray()).
The params keyword allows you to dynamically pass a variable number of arguments to the function without worrying about compiler errors like this:
public string PrefixFormatString(string p, string s, params object[] par)
{
return p + string.Format(s, par);
}
...
PrefixFormatString("COM", "Output Error #{0} - Error = {1}", errNum, errStr);
If you pass a list, you have to construct the list before you can pass it in:
public string PrefixFormatString(string p, string s, List<object> par)
{
return p + string.Format(s, par.ToArray());
}
...
List<object> l = new List<object>(new object[] { errNum, errStr });
PrefixFormatString("COM", "Output Error #{0} - Error = {1}", l);
and it tends to hide the meaning of what type of data the function is expecting.
Note that this is very similar to passing a simple array variable. The only difference is that the compiler will fix up the parameters into an array for you... I'm not 100% sure, but I think the technical difference is just syntactical sugar - in either case you are really passing an array of whatever type the parameter is.
params is a language construct for functions taking a variable number of parameters. It is similar to C elipses specifier - i.e. printf(char* fmt, ...). The language supports this kind of operation, might as well use it, especially if it makes the code easier to read.
Personally, I would skip the params. I've been bitten by it once or twice. How? Let me explain.
You write a public method with this signature:
public static void LogInUser(string username, string password, params string[] options)
You test it, it works, its done... and another assembly/application is calling your function.
Now, a month later you want to change your signature to add user role:
public static void LogInUser(string username, string password, string role, params string[] options)
Oh how things have changed for anything calling your method.
LogInUser("z#z.com", "zz", "Admin", "rememberMe", "800x600");
The main difference between the two that I can see is that the number of parameters passed into the method is set at compile time using params, while with a List<T> it depends on the list passed in at runtime.
Whether fixing the number of arguments that your method must be called with at compile time is a pro or a con depends entirely upon your design and intent for it. Either can be a benefit depending on what you are hoping to achieve.
Params helps on the readability front, and is as close to an optional parameter that you're going to get to in C#. I would only use the List<T> implementation personally if I needed to consume an unknown number of parameters at any point.
Edit: just spotted your edit regarding performance issues. On that topic I'm unsure, although if you could potentially expect a large number of 'parameters' using List<T>, whereas params has a sanity cap on it due to them having to be coded.
The performance of the programmer calling your method can sometimes be improved by your usages of the params keyword.
(Given that programmers cost so much more then computers, why are you thinking of any other sort of performance.)