I have a generic Array2D class and want to add an isEmpty getter. However T cannot be compared with default(T) via != in this case. And Equals() cannot be used since the field might be null (but see code below). How would I be able to check whether all fields are empty (i.e. the default value for the reference type or struct/etc.) or not?
So far I came up with the following solution but it already seems rather long-winded to me for a simple isEmpty check and it might not be the optimal way to solve this. Anyone know of a better solution?
public sealed class Array2D<T>
{
private T[,] _fields;
private int _width;
private int _height;
public bool isEmpty
{
get
{
for (int x = 0; x < _width; x++)
{
for (int y = 0; y < _height; y++)
{
if (_fields[x, y] != null && !_fields[x, y].Equals(default(T))) return false;
}
}
return true;
}
}
public Array2D(int width, int height)
{
_width = width < 0 ? 0 : width;
_height = height < 0 ? 0 : height;
_fields = new T[width, height];
}
}
Instead of looping through all of the elements in IsEmpty, simply update the value of IsEmpty when setting a value (requires an indexer but you'll probably need one anyway) :
public class Array2<T>
{
private readonly T[,] _array;
private bool _isEmpty;
public Array2(int width, int height)
{
_array = new T[width, height];
_isEmpty = true;
}
public T this[int x, int y]
{
get { return _array[x, y]; }
set
{
_array[x, y] = value;
_isEmpty = _isEmpty && value.Equals(default(T));
}
}
public bool IsEmpty
{
get { return _isEmpty; }
}
}
Example:
Array2<int> array2 = new Array2<int>(10, 10);
array2[0, 0] = 0;
Console.WriteLine(array2.IsEmpty);
array2[0, 0] = 1;
Console.WriteLine(array2.IsEmpty);
array2[0, 0] = 0;
Console.WriteLine(array2.IsEmpty);
Output:
True
False
False
Obviously this is a trade-off, but imagine that you have 1 million elements in the array, your loop runs 1 million times. With this approach there's no loop, only checking for 2 conditions at any time.
Related
So I got this base class
abstract class Item
{
private int x, y, ataque, defesa, saude, raridade;
private char appearance;
private bool pickedUp;
private readonly Random rng = new Random();
public Item(Map argMap, int argAtaque, int argDefesa, int argSaude, int argRaridade, char argAppearance)
{
bool empty = false;
while (!empty)
{
x = rng.Next(1, argMap.ReLengthX() - 1);
y = rng.Next(1, argMap.ReLengthY() - 1);
if (!argMap.CheckTile(y, x)) empty = true;
}
pickedUp = false;
ataque = argAtaque;
defesa = argDefesa;
saude = argSaude;
raridade = argRaridade;
appearance = argAppearance;
}
}
And I got this derived class
class Armadura : Item
{
public Armadura(Map argMap, int ataque, int defesa, int saude, int raridade, char appearance) : base(argMap, ataque, defesa, saude, raridade, appearance)
{
ataque = -1;
defesa = 2;
saude = 0;
raridade = ReRNG().Next(Convert.ToInt32(Math.Round(argMap.ReLengthY() * 0.02)), Convert.ToInt32(Math.Round(argMap.ReLengthY() * 0.04)));
appearance = ' ';
}
}
So my question is, how do I get to set the values on :base, using the values that I set on the derived constructor (for example, set the base argAtaque with ataquewwww, therefore, argAtaque being equal to '-1')?
I tried this using a few ways but I didn't get this to work in any way.
I thank you in advance!
The : base() syntax will work for constants and parameters, but not for more complex expressions with side-effects (as you found).
You'll be needing a initialization method on the base class.
abstract class Item
{
...
// If you use this constructor, call Setup() afterwards.
public Item() {}
// Constructor including a call to Setup()
public Item(Map argMap, int argAtaque, int argDefesa, int argSaude, int argRaridade, char argAppearance)
{
Setup(argMap, argAtaque, argDefesa, argSaude, argRaridade, argAppearance);
}
// Sets private variables for this Item
protected void Setup(Map argMap, int argAtaque, int argDefesa, int argSaude, int argRaridade, char argAppearance)
{
bool empty = false;
while (!empty)
{
x = rng.Next(1, argMap.ReLengthX() - 1);
y = rng.Next(1, argMap.ReLengthY() - 1);
if (!argMap.CheckTile(y, x)) empty = true;
}
pickedUp = false;
ataque = argAtaque;
defesa = argDefesa;
saude = argSaude;
raridade = argRaridade;
appearance = argAppearance;
}
}
Now you can setup the base class with the calculated values:
class Armadura : Item
{
public Armadura(Map argMap)
{
int ataque = -1;
int defesa = 2;
int saude = 0;
int raridade = ReRNG().Next(Convert.ToInt32(Math.Round(argMap.ReLengthY() * 0.02)), Convert.ToInt32(Math.Round(argMap.ReLengthY() * 0.04)));
char appearance = ' ';
Setup(argMap, ataque, defesa, saude, raridade, appearance);
}
All you have to do is set ataque in the child constructor, as it will override what ataque is being set to in your base class. The base constructor is called first, then the child constructor.
For this to work, you will need to make your private variables protected in the base class. This will make them private in the child class.
public class CubicMatrix<Object?>
{
private int width;
private int height;
private int depth;
private Object[, ,] matrix;
public CubicMatrix(int inWidth, int inHeight, int inDepth)
{
width = inWidth;
height = inHeight;
depth = inDepth;
matrix = new Object[inWidth, inHeight, inDepth];
}
public void Add(Object toAdd, int x, int y, int z)
{
matrix[x, y, z] = toAdd;
}
public void Remove(int x, int y, int z)
{
matrix[x, y, z] = null;
}
public void Remove(Object toRemove)
{
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
for (int z = 0; z < depth; z++)
{
Object value = matrix[x, y, z];
bool match = value.Equals(toRemove);
if (match == false)
{
continue;
}
matrix[x, y, z] = null;
}
}
}
}
public IEnumerable<Object> Values
{
get
{
LinkedList<Object> allValues = new LinkedList<Object>();
foreach (Object entry in matrix)
{
allValues.AddLast(entry);
}
return allValues.AsEnumerable<Object>();
}
}
public Object this[int x, int y, int z]
{
get
{
return matrix[x, y, z];
}
}
public IEnumerable<Object> RangeInclusive(int x1, int x2, int y1, int y2, int z1, int z2)
{
LinkedList<Object> list = new LinkedList<object>();
for (int a = x1; a <= x2; a++)
{
for (int b = y1; b <= y2; b++)
{
for (int c = z1; c <= z2; c++)
{
Object toAdd = matrix[a, b, c];
list.AddLast(toAdd);
}
}
}
return list.AsEnumerable<Object>();
}
public bool Available(int x, int y, int z)
{
Object toCheck = matrix[x, y, z];
if (toCheck != null)
{
return false;
}
return true;
}
}
I've created a Cubic Matrix class in C# to store items in 3 dimensions. I need to be able to add and remove items which is why I'm using Object? (I've been led to understand that you can't use nullable generics ie. T?). However this approach gnerates an error
Type parameter declaration must be an identifier not a type
If i don't use Object? though and just use Object or T i get this error instead
Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
What's the correct syntax and approach to use in this case?
If you want to restrict your generic type to objects only - i.e. no structs or simple types - you can add the where clause
public class CubicMatrix<T> where T : class
These means that T can only be a class.
When returning default(T) instead (as the error you are getting suggests), reference types will return null, numeric types will return 0, your custom classes will return null and nullables will return System.Nullable<T>. More about this in default Keyword in Generic Code (C# Programming Guide)
on MSDN.
I think you want to use the generic parameter T. You're making a simple container class, so allowing any generic parameter makes sense, whether it's nullable or not. To fix the error, just do what it says and use default(T) instead of null.
The error is because T could be a class or a struct, and structs can't be null. Therefore assigning a variable with type T to null is invalid. default(T) is null when T is a class and default values when T is a struct.
I have a generic type called Vector<T>, I created it as so, cause the T might be float or Complex :
public class Vector<T>
{
#region Properties
public ulong Length
{
get
{
return _length;
}
}
public VectorType VectorType
{
get
{
return _vectorType;
}
}
#endregion
#region Indexers
public T this[ulong index]
{
get
{
return _data[index];
}
set
{
_data[index] = value;
}
}
#endregion
#region Constructors
public Vector(VectorType vectorType, T[] data)
{
if (!((data is float[]) || (data is Complex[])))
{
throw new InvalidDataException("Data must be array of float or array of Complex");
}
_data = new T[_length = (ulong)data.Length];
for (ulong i = 0; i < _length; i++)
{
_data[i] = data[i];
}
_vectorType = vectorType;
}
public Vector(VectorType vectorType, Vector<T> vector)
{
_data = new T[_length = vector.Length];
for (ulong i = 0; i < _length; i++)
{
_data[i] = vector[i];
}
_vectorType = vectorType;
}
#endregion
#region Methods
//Unity Matrix, this vector has 1/N everywhere
public static Vector<float> e(VectorType vectorType, ulong length)
{
var data = new float[length];
for (ulong i = 0; i < length; i++)
{
data[i] = (float)1 / length;
}
var vectorE = new Vector<float>(vectorType, data);
return vectorE;
}
public float Sum()
{
float sum = 0;
if (_data is float[])
{
sum = (_data as float[]).Sum();
}
else
{
if (_data is Complex[])
{
for (ulong i = 0; i < _length; i++)
{
sum += (float)
Math.Sqrt(Math.Pow((_data[i] as Complex?).Value.Real, 2) +
Math.Pow((_data[i] as Complex?).Value.Imaginary, 2));
}
}
}
return sum;
}
public bool CheckIfSochasitc()
{
return Math.Abs(Sum() - 1) < float.Epsilon;
}
public void Normalize()
{
var sum = Sum();
if (_data is float[])
{
for (ulong i = 0; i < _length; i++)
{
float x = ((float) _data[i])/sum;
_data[i] = (T)x;
}
}
}
#endregion
#region Operators
//I omitted the code inhere to avoid overload
#endregion
#region Fields
private ulong _length;
private readonly VectorType _vectorType;
private T[] _data;
#endregion
}
public enum VectorType
{
Row,Column
}
My problem is that I have a generic array (if I can call it so) :
private T[] _data;
And I have the Normalize() method:
public void Normalize()
{
var sum = Sum();
if (_data is float[])
{
for (ulong i = 0; i < _length; i++)
{
//Here is the problem
_data[i] = ((_data[i] as float?) / sum);
}
}
}
This doesn't work saying can't cast float to T tried to search but couldn't find helpful aide, any clarification I'd be thankful.
Update :
The Sum() method always returns a float
It's not clear why you're converting to float? at all (or why you're using ulong as the index variable type...) but you just need to cast the result back to T - otherwise you can't assign it back into an array of type T[]. Additionally, you need to cast to object (in order to convert back to T:
float x = ((float) (object) data[i]) / sum;
data[i] = (T) (object) x;
You can use float? for the first line, with as, to avoid boxing - but then you need to get the non-nullable value:
float x = (data[i] as float?).Value / sum;
Both are pretty ugly :(
As noted in comments though, this sort of thing is usually an indication of the design not really being properly generic at all. We don't know what type Sum() returns, but you should consider just how "general" your type is to start with.
May be you can try this
if (typeof(_data) == float[])
{
for (ulong i = 0; i < _length; i++)
{
_data[i] = ((_data[i] as float?) / sum);
}
}
I'm learning to use the as operator, and my goal was to create an option window (non windows form) that can:
Have options added to it (for flexibility, in case I want to use if statements to add menu items)
Be able to display text, textures, or a class (using the classes draw function)
Be controlled through the host GameState
I still haven't added the options for indicating an item is selected, my apologies for not posting a complete work. I also have not sorted the code into regions yet. Sorry again!
Is my code (particularly the draw function) properly using the is and as operators properly, from a performance and readability (non spaghetti code) standpoint?
public class OptionWindow : DrawableGameComponent
{
public Dictionary<int, Option> options;
int selectedOption;
bool windowLoops;
Rectangle drawRectangle;
int spacer;
int totalItemHeight;
SpriteFont sf;
SpriteBatch sb;
public Rectangle DrawRectangle
{
get { return drawRectangle; }
set { drawRectangle = value; }
}
public int SelectedOption
{
get { return selectedOption; }
set
{
if (windowLoops)
{
if (selectedOption >= options.Count())
selectedOption = 0;
if (selectedOption < 0)
selectedOption = options.Count() - 1;
}
else
{
if (selectedOption >= options.Count())
selectedOption = options.Count() - 1;
if (selectedOption < 0)
selectedOption = 0;
}
}
}
public OptionWindow(Game game, bool windowLoops, SpriteFont sf, Rectangle drawRectangle)
: base(game)
{
options = new Dictionary<int, Option>();
this.windowLoops = windowLoops;
this.sf = sf;
DrawRectangle = new Rectangle(drawRectangle.X, drawRectangle.Y, drawRectangle.Width, drawRectangle.Height);
}
public void Add(object option, bool selectable, bool defaultSelection, int height)
{
options.Add(options.Count(), new Option(selectable, option, height));
if (defaultSelection)
SelectedOption = options.Count() - 1;
UpdatePositions();
}
public void UpdatePositions()
{
UpdateTotalItemHeight();
if (options.Count() - 1 != 0)
spacer = (drawRectangle.Height - totalItemHeight) / (options.Count() - 1);
for (int i = 0; i < options.Count(); i++)
{
if (i == 0)
options[i].Position = new Vector2(drawRectangle.X, drawRectangle.Y);
else
{
options[i].Position = new Vector2(
drawRectangle.X,
options[i - 1].Position.Y + options[i - 1].Height + spacer);
}
}
}
public void UpdateTotalItemHeight()
{
totalItemHeight = 0;
for (int i = 0; i < options.Count(); i++)
{
totalItemHeight += options[i].Height;
}
}
protected override void LoadContent()
{
sb = new SpriteBatch(GraphicsDevice);
base.LoadContent();
}
public override void Draw(GameTime gameTime)
{
for (int i = 0; i < options.Count(); i++)
{
if (options[i].OptionObject is string)
sb.DrawString(sf, options[i].OptionObject as string, options[i].Position, Color.White);
if (options[i].OptionObject is Texture2D)
sb.Draw(options[i].OptionObject as Texture2D,
new Rectangle(
(int)options[i].Position.X,
(int)options[i].Position.Y,
options[i].Height,
(options[i].Height / (options[i].OptionObject as Texture2D).Height) * (options[i].OptionObject as Texture2D).Width),
Color.White);
if (options[i].OptionObject is DisplayObject)
(options[i].OptionObject as DisplayObject).Draw(gameTime);
}
base.Draw(gameTime);
}
}
public class Option
{
bool selectable;
object optionObject;
int height;
Vector2 position;
public bool Selectable
{
get { return selectable; }
set { selectable = value; }
}
public object OptionObject
{
get { return optionObject; }
set { optionObject = value; }
}
public int Height
{
get { return height; }
set { height = value; }
}
public Vector2 Position
{
get { return position; }
set { position = value; }
}
public Option(bool selectable, object option, int height)
{
Selectable = selectable;
OptionObject = option;
Height = height;
}
}
It is never adviseable to use is and then as. The usual way to go would be to either of the following:
just use is (if you just want to know the type without subsequent casting)
assign the result of as to a variable and check whether that variable is (not) null
The code analysis tool FxCop helps you find any spots in your code that use is and then as and warns you because of performance concerns.
Note however that a better approach altogether might be to declare your OptionObject property as some abstract class with a Draw method. You could then derive a subclass for strings, one for Texture2D instances and another one for DisplayObject instances and just call Draw in your OptionWindow.Draw method. This would leave the decision which actual drawing operations to execute up to built-in polymorphism features of the framework.
I am trying to modify value in dictionary, but the compiler throws KeyNotFoundException. I'm sure, I declared that key in dictionary, because I am calling GenerateEmptyChunks() method, which fills dictionary with chunks with key of their position and values are empty for level generator. I've checked debugger and Chunks dictionary object is correctly filled with keys and values. Is it caused by my unworking CompareTo method? If yes, how I have modify CompareTo method to return right values?
public Dictionary<WPoint, WChunk> Chunks = new Dictionary<WPoint, WChunk>();
GenerateEmptyChunks() method:
public void GenerateEmptyChunks(int Xcount, int Ycount)
{
for(int x = 0; x <= Xcount; x++)
{
for (int y = 0; y <= Ycount; y++)
{
this.Chunks.Add(new WPoint(x, y), new WChunk(x, y));
}
}
}
AddBlock() method which is called by level generator for each tile:
public void AddBlock(WPoint location, int data)
{
this.Chunks[location.GetChunk()].AddTile(new WTile(location, data));
}
WChunk object:
public class WChunk
{
public int ChunkX;
public int ChunkY;
public SortedDictionary<WPoint, WTile> Tiles = new SortedDictionary<WPoint, WTile>();
public WChunk(int posX, int posY)
{
ChunkX = posX;
ChunkY = posY;
}
public void AddTile(WTile tile)
{
Tiles.Add(tile.GetLocation(), tile);
}
}
WPoint object:
public class WPoint : IComparable
{
public float X;
public float Y;
public WPoint(float x, float y)
{
X = x;
Y = y;
}
public WPoint GetChunk()
{
//Oprava pre bloky mensie ako (1,1)
if (X <= 16 && Y <= 16)
{
return new WPoint(0, 0);
}
else
{
double pX = (double)(X / 16);
double pY = (double)(Y / 16);
return new WPoint((int)Math.Floor(pX), (int)Math.Floor(pY));
}
}
public int CompareTo(object obj)
{
WPoint point2 = (WPoint)obj;
if (point2.X == this.X && point2.Y == this.Y)
{
return 0;
}
else if (point2.X >= this.X && point2.Y >= this.Y)
{
return -1;
}
else
{
return 1;
}
}
}
Any ideas why is compiler rejecting keys, when they are in dictionary?
Yes. You have not overridden GetHashCode.
Dictionary is using the GetHashCode and Equals for key comparisons, so implementing the IComparable interface is not enough. Have a look at this answer, that's exactly what you need.