* Initial guidelines * Add solution level directory with github related files for access within VS 2022 * Updated guidelines for extension methods - For types within MonoGame.Extended, prefer static methods within the type rather than extension methods - Extension methods should be used for extending external types from MonoGame or other libraries used.
9.3 KiB
MonoGame.Extended Coding Guidelines
Introduction
As MonoGame.Extended evolves, we need consistent coding conventions to ensure maintainability and readability. This document outlines the standards expected to be followed when contributing to MonoGame.Extended.
These guidelines follow the general principle of "use Visual Studio defaults" and align with standard .NET coding conventions while addressing specific needs of the MonoGame.Extended library.
Code Formatting
-
Braces: Use Allman style braces, where each brace begins on a new line.
-
Indentation: Use four spaces of indentation (no tabs).
-
Single-line Statements: All
if/else if/elseblocks should always use braces, even for single statements. This maintains consistency across the codebase.// correct if (foo == true) { DoSomething(); } // incorrect if (foo == true) DoSomething(); -
Line Spacing: Avoid more than one empty line at any time. Do not have two blank lines between members of a type.
-
White Space: Avoid spurious free spaces (e.g., avoid
if (someVar == 0)...). -
This Keyword: Avoid
this.unless absolutely necessary.
Naming Conventions
Type Naming
- Classes and Structs: PascalCase (e.g.,
HslColor,ParticleEmitter) - Interfaces: Prefix with "I" + PascalCase (e.g.,
IGameComponent,IUpdateable) - Enums: PascalCase for type and values (e.g.,
BlendMode.Additive) - Type Parameters: Single capital letter (T) or prefixed with "T" (e.g.,
TKey,TValue) - Make all internal and private types static or sealed unless derivation is required.
Member Naming
- Methods: PascalCase for all method names, including local functions (e.g.,
Update(),Draw()) - Properties: PascalCase (e.g.,
Position,Rotation) - Private/Internal Fields:
- Use
_camelCasefor internal and private instance fields (e.g.,_particles,_isActive) - Prefix static fields with
s_(e.g.,s_defaultSize) - Prefix thread static fields with
t_(e.g.,t_instance) - Use
readonlywhere possible (afterstaticwhen used together)
- Use
- Public Fields: Use PascalCasing with no prefix (use public fields sparingly; prefer properties)
- Constants: Use CONSTANT_CASE (e.g.,
MAX_PARTICLES,DEFAULT_DURATION) - Events: PascalCase with "EventHandler" suffix for delegate types (e.g.,
CollisionEventHandler)
Extension Methods
Preferred Approach for MonoGame.Extended Types:
- For types that are part of MonoGame.Extended (where we control the source code), prefer static methods within the type itself rather than extension methods
- Extension methods should primarily be used for extending types from MonoGame core or external libraries where we don't control the source
When Extension Methods Are Necessary:
- Extension Class Naming:
{TypeName}Extensions(e.g.,ColorExtensions,Vector2Extensions) - One Extension Class Per Type: Each type being extended should have its own extension class
- No Mixed Extensions: Don't extend multiple types in a single extension class
Conversion Methods
- Method Naming Standard:
- Use
From{SourceType}for static factory methods on the target type- Example:
TargetType.FromSourceType(source)creates aTargetTypefrom aSourceType
- Example:
- Use
To{TargetType}for extension methods on the source type- Example:
source.ToTargetType()as an extension method onSourceTypereturns aTargetType
- Example:
- Helper classes should use
{SourceType}To{TargetType}naming for conversion methods- Example:
ConversionHelper.SourceTypeToTargetType(source)
- Example:
- Use
Helper Classes
- Helper Class Naming:
{Domain}Helper(e.g.,ColorHelper,MathHelper) - Purpose: For utility methods that don't belong to a specific type
- Static Only: Helper classes should be static classes with static methods
- No Instance State: Helper classes should not maintain instance state
Declaration and Usage Guidelines
-
Visibility: Always specify visibility, even if it's the default (e.g.,
private string _foonotstring _foo).- Visibility should be the first modifier (e.g.,
public abstractnotabstract public).
- Visibility should be the first modifier (e.g.,
-
Namespace Imports:
-
Specify at the top of the file, outside of
namespacedeclarations -
Order as follows:
System.*namespaces- MonoGame types at
Microsoft.Xna.Framework.* - MonoGame.Extended types
-
Example:
using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using MonoGame.Extended.Graphics; using MonoGame.Extended.Particles;
-
-
Type References:
- Use language keywords instead of BCL types (e.g.,
int, string, floatinstead ofInt32, String, Single) - This applies to both type references and method calls (e.g.,
int.Parseinstead ofInt32.Parse)
- Use language keywords instead of BCL types (e.g.,
-
Variable Declarations:
- Highly discourage the use of
var. C# is a strongly typed language, and using explicit types improves code readability. - Use
varonly when absolutely necessary, not as a general practice. - Target-typed
new()can only be used when the type is explicitly named on the left-hand side (e.g.,SourceType source = new())
- Highly discourage the use of
-
String References: Use
nameof(...)instead of"..."whenever possible and relevant.
Code Organization
Namespace Structure
- Mirror MonoGame Structure: Follow the same organizational patterns as the core MonoGame framework
- Graphics-related classes should be in
MonoGame.Extended.Graphics(corresponding to MonoGame'sMicrosoft.Xna.Framework.Graphics) - Input-related classes should be in
MonoGame.Extended.Input(corresponding to MonoGame'sMicrosoft.Xna.Framework.Input) - Math-related utilities should be in
MonoGame.Extendeddirectly (corresponding to MonoGame'sMicrosoft.Xna.Framework) - Audio extensions should be in
MonoGame.Extended.Audio(corresponding to MonoGame'sMicrosoft.Xna.Framework.Audio)
- Graphics-related classes should be in
- Core Namespace:
MonoGame.Extendedfor fundamental extensions and types - Domain-Specific Namespaces:
MonoGame.Extended.{Domain}for features that extend beyond MonoGame's core- Example:
MonoGame.Extended.Particles - Example:
MonoGame.Extended.Tiled
- Example:
File and Class Organization
- File Layout: One type per file with matching filename
- Fields: Fields should be specified at the top within type declarations
- Member Ordering:
- Fields (private, then protected, then public)
- Properties
- Constructors
- Methods (grouped by functionality)
- Nested types
Documentation
- Public APIs: All public APIs must have XML documentation comments
- Parameter Documentation: Document all parameters with
<param>tags - Return Value: Document return values with
<returns>tags - Exceptions: Document exceptions with
<exception>tags
Error Handling
- Validation:
- Validate parameters using
ArgumentNullException.ThrowIfNulland similar methods where available.
- Validate parameters using
- Avoid Swallowing Exceptions: Do not catch exceptions without handling or re-throwing
Implementation Examples
Extension Methods
// CORRECT: Dedicated extension class for Foo
public static class FooExtensions
{
public static Bar ToBar(this Foo foo)
{
// Implementation
}
}
// CORRECT: Dedicated extension class for Bar
public static class BarExtensions
{
public static Foo ToFoo(this Bar bar)
{
// Implementation
}
}
Conversion Methods
// CORRECT: Factory method on target type
public struct TargetType
{
public static TargetType FromSourceType(SourceType source)
{
// Implementation
}
}
// CORRECT: Extension method on source type
public static class SourceTypeExtensions
{
public static TargetType ToTargetType(this SourceType source)
{
// Implementation
}
}
// CORRECT: Helper class method naming
public static class ConversionHelper
{
public static TargetType SourceTypeToTargetType(SourceType source)
{
// Implementation
}
public static SourceType TargetTypeToSourceType(TargetType target)
{
// Implementation
}
}
Example File Structure
Below is an example following our style guidelines:
ExampleClass.cs:
using System;
using System.Collections.Generic;
namespace Example
{
public partial class ExampleClass
{
private readonly List<Item> _items;
private float _rate;
public int Count
{
get { return _items.Count; }
}
public bool IsEnabled { get; private set; }
public ExampleClass(int capacity)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity);
_items = new List<Item>(capacity);
IsEnabled = true;
}
public void Update(GameTime gameTime)
{
if (!IsEnabled)
{
return;
}
UpdateItems();
CreateItems();
}
private void UpdateItems(GameTime gameTime)
{
// Implementation
}
private void CreateItems(GameTime gameTime)
{
// Implementation
}
}
}