I was disappointed to discover that C#'s "with expressions" allow the caller to bypass constructor validations on the record.
Consider:
record AscendingPair {
public AscendingPair(int small, int large)
{
if (small >= large) throw new ArgumentException("");
(Small, Large) = (small, large);
}
public int Small { get; init; }
public int Large { get; init; }
}
[Test]
public void Can_create_an_invalid_pair() {
var valid = new AscendingPair(1, 2);
var invalid = valid with { Small = 3 }; // This does not throw :(
}
Is there a smart workaround that would allow use of with, but still enforce the validation?
The with expression is lowered into something like this (check this on SharpLab):
var temp = valid.<Clone>$(); // you can't actually access this from C#
temp.Small = 3;
var invalid = temp;
This is documented here:
First, receiver's "clone" method (specified above) is invoked and its result is converted to the receiver's type. Then, each member_initializer is processed the same way as an assignment to a field or property access of the result of the conversion. Assignments are processed in lexical order.
Notes:
valid.<Clone$>() just calls the copy constructor of the record (new AscendingPair(valid)).
It is possible to set temp.Small here because you are doing it in the member initialiser list, which is one of the places where you can set init-only properties.
Now it should be clear how exactly the with expression bypasses the check in your constructor.
One way to solve this is to move the check to the init accessors:
record AscendingPair {
public AscendingPair(int small, int large)
{
if (small >= large) throw new ArgumentException("");
(Small, Large) = (small, large);
}
private int small;
private int large;
public int Small {
get => small;
init {
if (value >= large) {
throw new ArgumentException("");
}
small = value;
}
}
public int Large {
get => large;
init {
if (small >= value) {
throw new ArgumentException("");
}
large = value;
}
}
}
There is an important caveat to this fix though: the order of the assignments in the with expression now matters. This is a natural consequence of how the with expression is lowered, with each assignment being "processed in lexical order". For example:
var valid = new AscendingPair(1, 2);
var invalid = valid with { Large = 4, Small = 3 };
is fine, but,
var valid = new AscendingPair(1, 2);
var invalid = valid with { Small = 3, Large = 4 };
throws an exception.
We can't really do anything about this though, because to solve this problem, we would need to move the check to after all the assignments of the with expression have completed, but as far as I know, we can't really know when that is inside the record. The lowered code does not call an extra method or anything like that at the end of the series of assignments.
Related
Skip to the third paragraph for the question.
Context: I'm creating a 2D spaceship game, and one of the game mechanics is routing power to the 7 different parts of the ship to suit the current situation you're in. There are presets that you can switch to at any time, called power configurations. I have the power configurations stored in 3 different arrays, like so:
int[] powerConfiguration1 = new int[7] {10,10,10,10,20,20,20};
int[] powerConfiguration2 = new int[7] {20,20,20,10,10,10,10};
int[] powerConfiguration3 = new int[7] {10,20,10,20,10,20,10};
When you switch configurations, it calls a method for doing so. The method makes some calculations to determine how long it will take to switch configurations. However, instead of making a switch statement and copy/pasting the code several times in the case of the player switching to any of the three configurations, I want to use PropertyInfo in System.Reflections to choose which property I need to pull values from.
Question: The problem is that I don't know how to get an item from an array. Here is what I have so far, where I'm attempting to determine how much power will need to be rerouted in total and adding it all to a variable. 0 is the index in the configuration at which I have decided to store the shield power. powerToShields is the current power being routed to the shields.
void switchConfiguration(int number) {
PropertyInfo powerConfiguration = GetType().GetProperty("powerConfiguration" + number);
int powerToReroute = 0;
powerToReroute += Mathf.Abs(powerToShields - powerConfiguration[0]);
Could someone please explain what I'm doing wrong and/or show me how to fix it? Or, is there a better way to do this?
EDIT 1: This is coded in C# (Unity).
Side thought
I guess my first question is why not store the arrays in a list. So, instead of powerConfiguration1, powerConfiguration2, powerConfiguration3, why not just store a list of int[], so
List<int[]> powerConfigurationList = new List<int[]>;
powerConfigurationList.Add(new int[7] {10,10,10,10,20,20,20});
powerConfigurationList.Add(new int[7] {20,20,20,10,10,10,10});
powerConfigurationList.Add(new int[7] {10,20,10,20,10,20,10});
That way you can get the item via:
powerToReroute = (powerConfigurationList[number])[0]
Answer to your question
However, assuming that there is some good reason that you can't, and in order to answer your exact question, do the following:
...
PropertyInfo powerConfiguration = GetType().GetProperty("powerConfiguration" + number); //this line is taken from your example above
//then you need to do something like the below
var value = (int[])powerConfiguration.GetValue(instanceThatHasTheProperty);
int powerToReroute = 0;
powerToReroute += Mathf.Abs(powerToShields - value[0]);
From your code snippet, I see you have GetType().GetProperty("powerConfiguration" + number);. I'm not sure what the actual instance is that your getting that type from. So you need to replace instanceThatHasTheProperty in my above snippet, by whatever instance you're trying to get the property's value from.
The thing that is immediately obvious to me is that the code is unnecessarily obfuscated and seems an awful lot like an X-Y problem. Using jagged arrays and reflection seems like a whole lot of work to end-around object oriented programming. My recommendation would be to create a class to store your power configurations, store multiple power configurations in a list, and then select the configuration from the list.
Sample Class for Power Configurations
public class PowerConfiguration
{
public int ID { get; set; }
public string Name { get; set; }
public int Shields { get; set; }
public int Weapons { get; set; }
public int LifeSupport { get; set; }
public int Propulsion { get; set; }
}
Inserting and Accessing your Power Configurations
public class DoStuff
{
public void LoadPowerConfiguration()
{
// Create a List to store configurations
List<PowerConfiguration> allPowerConfigurations = new List<PowerConfiguration>();
// Add some mock data to the list
allPowerConfigurations.Add(new PowerConfiguration()
{
ID = 0,
Name = "Balanced Combat",
Shields = 30,
Weapons = 30,
LifeSupport = 20,
Propulsion = 20
});
allPowerConfigurations.Add(new PowerConfiguration()
{
ID = 1,
Name = "Offensive",
Shields = 20,
Weapons = 50,
LifeSupport = 10,
Propulsion = 20
});
// Figure out which ID you what (eg. from the user pressing '0')
int selectedConfigurationID = 0;
// Get the configuration from the list
PowerConfiguration selectedConfiguration =
allPowerConfigurations.FirstOrDefault(p => p.ID == selectedConfigurationID);
// Now perform your operations against the PowerConfiguration object's properties
int powerToShields = 100;
int powerToReroute = 0;
powerToReroute += Math.Abs(powerToShields - selectedConfiguration.Shields);
}
}
Example, I have a random number, but it can have "special numbers" values. Something like that:
enum XNumber { INFINITY, NEGATIVE, int }
So I could store:
var i = XNumber.INFINITY;
var i = XNumber.NEGATIVE;
var i = (XNumber) 1;
var i = (XNumber) 500;
var i = (XNumber) -1000;
If not, what are my possibilities to do that?
An enum can be cast to/from an int provided the values match up. Note that you can assign numeric values to enum values. For instance,
namespace Test
{
enum SpecialValue
{
Zero = 0,
Five = 5,
Seventy = 70
}
private void method()
{
var five = (SpecialValue)5; // == SpecialValue.Five
int seventy = (int)SpecialValue.Seventy; // == 70
}
}
The easiest way is probably putting both the int and the enum into a struct. You can define conversion operators and arithmetic operators to make working with your type easier.
e.g.
struct XNumber {
private enum SpecialValue { Normal, Negative, Infinity}
private int value;
private SpecialValue kind;
public XNumber Infinity = new XNumber { kind = SpecialValue.Infinity };
public XNumber Negative = new XNumber { kind = SpecialValue.Negative };
public static implicit operator XNumber(int value) {
if (value < 0)
return new XNumber { kind = SpecialValue.Negative };
return new XNumber { value = value };
}
// ...
}
Something like that, depending on your exact needs.
Another way, if you don't need the full range of int, is to use special values for infinity and negative and you only need to store the int. Still a struct, you should still define conversions and operators so that everything matches up. While you can get away with just an enum, as Wai Ha Lee notes, I'd not recommend it, as you essentially have an own type that has its own semantics (just that those happen to be somewhat supported by enums in C#).
Is it somehow possible to get the reference of the result of an overloaded operator in C# so you don't have to use the "new" keyword to create a temp result (which is returned afterwards)?
Here's an example of a problem I ran into:
public class Stats {
public float someField;
public float someOtherField;
public static Stats operator +(Stats a, Stats b) {
Stats c = new Stats(); // I don't want a new one, can I access operators result directly?
c.someField = a.someField + b.someField;
c.someOtherField = a.someOtherField + b.someOtherField;
return c;
}
/*
// This is what I want to achieve, but it would be cooler if static and with the "+"
public Add(SomeType a) {
someField += a.someField;
someOtherField += a.someOtherField
}
*/
}
public class StatObserver {
public Stats statsToObserve;
public Output() {
print(statsToObserve.someField);
}
}
public class Class {
public Stats firstStats = new Stats();
firstStats.someField = 1.5f;
public StatObserver showStats = new StatObserver();
showStats.statsToObserve = firstStats;
public Stats nextStats = new Stats();
nextStats.someField = 3.4f;
// now the tricky part
firstStats += nextStats; // C# handles the += itself correctly
showStats.Output(); // prints "1.5"
// you have to update the observer to get the new value
// it's kind of stupid, because you have to treat firstStats like a value type buts its not
showStats.statsToObserve = firstStats;
showStats.Output(); // prints "4.9"
}
You can't overload the += operator directly - it is compiled to an add and an assignment. You could mutate the left-hand side as part of the + operator - but that would be evil. An Add method seems to be the cleanest design IMHO.
First things first, as #D.Stanley notes, you can't override +=. You can override +, as you have done, but there is something important to realize about +:
Math operators are non-destructive, that is, they return a result
without modifying the operands
That being said, you could modify the properties of operands in a reference type (which this is), but you shouldn't. So don't. The good news is, your + operation is correct.
Because of this, you have to return a new object (as you do), but when you do the += you assign the local reference to this new object, while leaving the observer's reference pointing at the old object, causing your error.
You probably want to modify the observer directly:
showStats.statsToObserve += nextStats;
Or, you could totally hack it and do this (not recommended):
public static Stats operator +(Stats a, Stats b) {
Stats c = new Stats();
c.someField = a.someField + b.someField;
a.SomeField += b.someField; //AHHHH You just modified an operand!
c.someOtherField = a.someOtherField + b.someOtherField;
a.someOtherField += b.someOtherField; //AHHHH You just did it again!
return c;
}
Note: I like #DStanley's recommendation as well for the solution to this.
I'm dealing with some legacy data, where they store each record in one huge/large string (one string = one record)
In each string, they split the data in some sort of delimiters, but each of them actually defines a meaning, for example: \vToyota\cBlue\cRed\cWhite\s200mph\oAndrew\oJohn
\v means vehicle, \c is color, \s is speed \o is Owner... something like that
My task requires me to reformat the data so that if there are multiple fields of one characteristic, I have to rewrite it as: (for example) \vToyota\cBlue\c2Red\c3White\s200mph\oAndrew\o2John
Edited: Alright. #DarrenYoung's suggestions works! Now I have an array of vToyota cBlue cRed cWhite s200mph oAndrew oJohn. I tested on other data using the same method and it is working too. Now I just need help to find a way to rewrite the first letter of each string whenever they are repeated.
Thank you!
I found this an interesting little puzzle to see what I could do with LINQ. The following seems to work:
private string FixIt(string foo)
{
var newFoo = "\\" + string.Join("\\",
foo.Split(new[] {'\\'}, StringSplitOptions.RemoveEmptyEntries)
.GroupBy(s => s[0],
(c, g) =>
{
var cnt = 0;
return g.Select(x => cnt++ == 0
? x
: x[0] + cnt.ToString() + x.Substring(1));
})
.SelectMany(g => g));
return newFoo;
}
Input: \vToyota\cBlue\cRed\cWhite\s200mph\oAndrew\oJohn
Output: \vToyota\cBlue\c2Red\c3White\s200mph\oAndrew\o2John
That SelectMany is a handy thing to remember.
Because I thought this question was interesting I wrote up a program to do what I believe to be a reasonable solution. I started with a few principle assumptions:
In "old data" situations you probably don't know every single option that is going to show up in the records. Consequently whatever approach is taken needs to quickly and easily accommodate new types of delimiters and tags. For that reason I did not use a string.split approach (even though this is easier to read). Instead all tokens are declared at the beginning of the file. Anything can be a token whether or not it has a "\" in front of it.
The solution needs to gracefully handle records that don't conform to the standards
The option of parsing integers for multiple records needs to be able to be disabled per record type. Speed, for example, doesn't (seem) to be able to appear multiple times per record. So, setting the value for speed to false in the "ALLOW_MULTIPLE" variable turns this parsing off, ensuring the correct output value.
In my solution I also created separate classes for readability and so the code could be quickly investigated. Although I would not suggest that this is production ready, the following should go a long ways towards solving the issue. Best of luck!
// Just paste the rest of this into a new console application to see it work!
public class Program
{
private static readonly List<string> TOKENS = new List<string> {#"\v", #"\c", #"\o", #"\s"};
private static readonly List<string> DISPLAY = new List<string> {"Vehicle", "Color", "Owner", "Speed"};
private static readonly List<bool> ALLOW_MULTIPLE = new List<bool> {false, true, true, false};
private class RecordEntry
{
public string Value { get; set; }
public int Index { get; set; }
public string DataType { get; set; }
public override string ToString() { return DataType + ": " + Value; }
}
private class ParsedRecord
{
private List<RecordEntry> entries = new List<RecordEntry>();
public List<RecordEntry> Entries { get { return entries; } }
}
public static void Main(string[] args)
{
// sample records (second has a \m which is ignored since it isn't a recognized token)
var records = new[] {#"\vToyota\cBlue\c2Red\c3White\s200mph\oAndrew\o2John",
#"\vChevy\c2Orange\cGreen\s50mph\o2Bob\mWhite"};
var parsedData = new List<ParsedRecord>();
foreach (var record in records)
{
// character by character parsing
var currentParseRecord = new ParsedRecord();
parsedData.Add(currentParseRecord);
var currentRecord = new StringBuilder(record);
var currentToken = new StringBuilder();
for (var parseIdx = 0; parseIdx < currentRecord.Length; parseIdx++)
{
currentToken.Append(currentRecord[parseIdx]);
var recordIdx = 0;
var index = TOKENS.IndexOf(currentToken.ToString());
if (index < 0) continue;
// current char is used up now (was part of the token)
parseIdx++;
if (ALLOW_MULTIPLE[index] && currentRecord.Length > parseIdx + 1)
{
// assuming less than 10 records max - if more, would need to pull multiple numeric values here
if (!Int32.TryParse(currentRecord[parseIdx] + "", out recordIdx)) recordIdx = 0;
else parseIdx++;
}
// find the next token or end of string
int valueLength = FindNextToken(currentRecord, parseIdx) - parseIdx;
if (valueLength <= 0) valueLength = currentRecord.Length - parseIdx;
currentParseRecord.Entries.Add(new RecordEntry
{
DataType = DISPLAY[index],
Index = recordIdx,
Value = currentRecord.ToString(parseIdx, valueLength)
});
parseIdx += valueLength - 1;
currentToken.Clear();
}
}
}
private static int FindNextToken(StringBuilder value, int currentIndex)
{
for (var searchIdx = currentIndex; searchIdx < value.Length; searchIdx++) {
if (TOKENS.Any(checkToken => value.Length > searchIdx + checkToken.Length &&
value.ToString(searchIdx, checkToken.Length) == checkToken)) {
return searchIdx;
}
}
return -1;
}
}
I were asked to do an StringToInt / Int.parse function on the white board in an job interview last week and did not perform very good but I came up with some sort of solution. Later when back home I made one in Visual Studion and I wonder if there are any better solution than mine below.
Have not bothred with any more error handling except checking that the string only contains digits.
private int StrToInt(string tmpString)
{
int tmpResult = 0;
System.Text.Encoding ascii = System.Text.Encoding.ASCII;
byte[] tmpByte = ascii.GetBytes(tmpString);
for (int i = 0; i <= tmpString.Length-1; i++)
{
// Check whatever the Character is an valid digit
if (tmpByte[i] > 47 && tmpByte[i] <= 58)
// Here I'm using the lenght-1 of the string to set the power and multiply this to the value
tmpResult += (tmpByte[i] - 48) * ((int)Math.Pow(10, (tmpString.Length-i)-1));
else
throw new Exception("Non valid character in string");
}
return tmpResult;
}
I'll take a contrarian approach.
public int? ToInt(this string mightBeInt)
{
int convertedInt;
if (int.TryParse(mightBeInt, out convertedInt))
{
return convertedInt;
}
return null;
}
After being told that this wasn't the point of the question, I'd argue that the question tests C coding skills, not C#. I'd further argue that treating strings as arrays of characters is a very bad habit in .NET, because strings are unicode, and in any application that might be globalized, making any assumption at all about character representations will get you in trouble, sooner or later. Further, the framework already provides a conversion method, and it will be more efficient and reliable than anything a developer would toss off in such a hurry. It's always a bad idea to re-invent framework functionality.
Then I would point out that by writing an extension method, I've created a very useful extension to the string class, something that I would actually use in production code.
If that argument loses me the job, I probably wouldn't want to work there anyway.
EDIT: As a couple of people have pointed out, I missed the "out" keyword in TryParse. Fixed.
Converting to a byte array is unnecessary, because a string is already an array of chars. Also, magic numbers such as 48 should be avoided in favor of readable constants such as '0'. Here's how I'd do it:
int result = 0;
for (int i = str.Length - 1, factor = 1; i >= 0; i--, factor *= 10)
result += (str[i] - '0') * factor;
For each character (starting from the end), add its numeric value times the correct power of 10 to the result. The power of 10 is calculated by multiplying it with 10 repeatedly, instead of unnecessarily using Math.Pow.
I think your solution is reasonably ok, but instead of doing math.pow, I would do:
tmpResult = 10 * tmpResult + (tmpByte[i] - 48);
Also, check the length against the length of tmpByte rather than tmpString. Not that it normally should matter, but it is rather odd to loop over one array while checking the length of another.
And, you could replace the for loop with a foreach statement.
If you want a simple non-framework using implementation, how 'bout this:
"1234".Aggregate(0, (s,c)=> c-'0'+10*s)
...and a note that you'd better be sure that the string consists solely of decimal digits before using this method.
Alternately, use an int? as the aggregate value to deal with error handling:
"12x34".Aggregate((int?)0, (s,c)=> c>='0'&&c<='9' ? c-'0'+10*s : null)
...this time with the note that empty strings evaluate to 0, which may not be most appropriate behavior - and no range checking or negative numbers are supported; both of which aren't hard to add but require unpretty looking wordy code :-).
Obviously, in practice you'd just use the built-in parsing methods. I actually use the following extension method and a bunch of nearly identical siblings in real projects:
public static int? ParseAsInt32(this string s, NumberStyles style, IFormatProvider provider) {
int val;
if (int.TryParse(s, style, provider, out val)) return val;
else return null;
}
Though this could be expressed slightly shorter using the ternary ? : operator doing so would mean relying on side-effects within an expression, which isn't a boon to readability in my experience.
Just because i like Linq:
string t = "1234";
var result = t.Select((c, i) => (c - '0') * Math.Pow(10, t.Length - i - 1)).Sum();
I agree with Cyclon Cat, they probably want someone who will utilize existing functionality.
But I would write the method a little bit different.
public int? ToInt(this string mightBeInt)
{
int number = 0;
if (Int32.TryParse(mightBeInt, out number))
return number;
return null;
}
Int32.TryParse does not allow properties to be given as out parameter.
I was asked this question over 9000 times on interviews :) This version is capable of handling negative numbers and handles other conditions very well:
public static int ToInt(string s)
{
bool isNegative = false, gotAnyDigit = false;
int result = 0;
foreach (var ch in s ?? "")
{
if(ch == '-' && !(gotAnyDigit || isNegative))
{
isNegative = true;
}
else if(char.IsDigit(ch))
{
result = result*10 + (ch - '0');
gotAnyDigit = true;
}
else
{
throw new ArgumentException("Not a number");
}
}
if (!gotAnyDigit)
throw new ArgumentException("Not a number");
return isNegative ? -result : result;
}
and a couple of lazy tests:
[TestFixture]
public class Tests
{
[Test]
public void CommonCases()
{
foreach (var sample in new[]
{
new {e = 123, s = "123"},
new {e = 110, s = "000110"},
new {e = -011000, s = "-011000"},
new {e = 0, s = "0"},
new {e = 1, s = "1"},
new {e = -2, s = "-2"},
new {e = -12223, s = "-12223"},
new {e = int.MaxValue, s = int.MaxValue.ToString()},
new {e = int.MinValue, s = int.MinValue.ToString()}
})
{
Assert.AreEqual(sample.e, Impl.ToInt(sample.s));
}
}
[Test]
public void BadCases()
{
var samples = new[] { "1231a", null, "", "a", "-a", "-", "12-23", "--1" };
var errCount = 0;
foreach (var sample in samples)
{
try
{
Impl.ToInt(sample);
}
catch(ArgumentException)
{
errCount++;
}
}
Assert.AreEqual(samples.Length, errCount);
}
}