Why Modern C# Matters for Unity Developers
Unity developers have been writing C# code for years, often falling into familiar patterns that, while functional, can become verbose and harder to maintain over time. Modern C# has introduced features that align perfectly with game development needs--cleaner syntax, reduced boilerplate, and more expressive code that communicates intent clearly.
According to LogRocket's analysis of modern C# features for Unity, these language enhancements help write more concise and readable code in Unity projects. Whether you're building a small indie project or a large-scale game, these features can help you write code that's easier to read, debug, and extend.
This guide explores six modern C# features that every Unity developer should incorporate into their workflow. Our team specializes in helping studios adopt modern development practices that improve code quality across all their projects. From web applications to complex game systems, our experienced developers understand how to leverage modern language features for better results.
Six modern C# features that transform your Unity development workflow
Switch Expressions
Replace verbose switch statements with concise expression syntax for cleaner control flow
Pattern Matching
Type-safe conditions that make conditional logic more readable and maintainable
Record Types
Value-based equality and immutability for game data models and configurations
Top-Level Statements
Eliminate boilerplate class declarations for simpler utility scripts
Global Using Directives
Reduce repetitive namespace declarations across all your Unity scripts
Primary Constructors
Streamlined class initialization with C# 12's concise constructor syntax
1. Switch Expressions: Elegant Control Flow
Why Switch Expressions Matter in Unity
Traditional switch statements in C# require significant boilerplate code--each case needs its own block, break statements, and often default handling. Switch expressions, introduced in C# 8, condense this into a single expression that returns a value directly. This is particularly valuable in Unity where you frequently need to handle different character states, item types, or game events.
1public class CharacterController : MonoBehaviour2{3 private enum CharacterState { Idle, Running, Jumping, Attacking }4 5 private string GetAnimationClipName(CharacterState state, bool isArmed)6 {7 switch (state)8 {9 case CharacterState.Idle:10 return isArmed ? "Idle_Armed" : "Idle_Unarmed";11 case CharacterState.Running:12 return isArmed ? "Run_Armed" : "Run_Unarmed";13 case CharacterState.Jumping:14 return "Jump";15 case CharacterState.Attacking:16 return isArmed ? "Attack_Weapon" : "Attack_Unarmed";17 default:18 return "Idle_Unarmed";19 }20 }21}1public class CharacterController : MonoBehaviour2{3 private enum CharacterState { Idle, Running, Jumping, Attacking }4 5 private string GetAnimationClipName(CharacterState state, bool isArmed) => state switch6 {7 CharacterState.Idle => isArmed ? "Idle_Armed" : "Idle_Unarmed",8 CharacterState.Running => isArmed ? "Run_Armed" : "Run_Unarmed",9 CharacterState.Jumping => "Jump",10 CharacterState.Attacking => isArmed ? "Attack_Weapon" : "Attack_Unarmed",11 _ => "Idle_Unarmed"12 };13}Practical Unity Applications
Switch expressions shine in Unity's event-driven architecture. Consider handling different power-up types in a collection system:
public class PowerUpCollector : MonoBehaviour
{
public int Collect(PowerUpType type) => type switch
{
PowerUpType.Health => AddHealth(25),
PowerUpType.Shield => AddShield(50),
PowerUpType.Speed => ApplySpeedBoost(1.5f, 10f),
PowerUpType.Weapon => UnlockWeapon(),
_ => 0
};
}
The pattern also works excellently for UI state management, input handling, and quest state transitions--all common scenarios in Unity projects. Our game development expertise encompasses these modern patterns across various Unity projects. When you're ready to level up your development workflow, our software development team can help you implement these practices across your entire codebase.
2. Pattern Matching: Type-Safe Conditions
The Power of Type Patterns
Pattern matching extends beyond simple value matching to inspect types and properties directly. In Unity, this is invaluable when working with polymorphic game objects, component-based architectures, or event systems that handle various message types.
1public class InteractionHandler : MonoBehaviour2{3 private void OnTriggerEnter(Collider other)4 {5 var health = other.GetComponent<Health>();6 if (health != null)7 {8 health.Restore(10);9 return;10 }11 12 var shield = other.GetComponent<Shield>();13 if (shield != null)14 {15 shield.Activate();16 return;17 }18 19 var collectible = other.GetComponent<ICollectible>();20 if (collectible != null)21 {22 collectible.Collect();23 return;24 }25 }26}1public class InteractionHandler : MonoBehaviour2{3 private void OnTriggerEnter(Collider other)4 {5 switch (other)6 {7 case Health health:8 health.Restore(10);9 break;10 case Shield shield when shield.CurrentCharge < 50:11 shield.Activate();12 break;13 case ICollectible collectible:14 collectible.Collect();15 break;16 }17 }18}Property Patterns for Game Logic
Property patterns allow matching based on object properties without additional variable declarations:
public class EnemyAI : MonoBehaviour
{
public void TakeDamage(float damage, DamageSource source)
{
if (source is { Type: DamageType.Fire, ElementalDamage: > 50f })
{
ApplyBurnEffect();
}
if (this.enemy is { Health: < 25, IsVulnerable: true })
{
TriggerFleeingBehavior();
}
}
}
This pattern is particularly useful in Unity's physics callbacks, event systems, and any scenario where you need to evaluate multiple conditions on objects with known structures.
3. Record Types: Value-Based Data Models
Why Records Excel for Game Data
Record types, introduced in C# 9 and improved in later versions, provide value-based equality comparison and immutability by default. For game development, this is ideal for inventory items, quest data, configuration objects, and any data structure where you want value semantics rather than reference semantics.
1// Traditional approach2public class ItemData : ScriptableObject3{4 public string itemId;5 public string displayName;6 public int maxStackSize;7 8 public override bool Equals(object obj)9 {10 if (obj is ItemData other)11 return itemId == other.itemId;12 return false;13 }14 15 public override int GetHashCode() => itemId.GetHashCode();16}17 18// Modern record approach19[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Item")]20public record ItemData(21 string ItemId,22 string DisplayName,23 int MaxStackSize = 9924)25{26 public bool IsStackable => MaxStackSize > 1;27}4. Top-Level Statements: Eliminating Boilerplate
Reducing File Bloat
Unity projects often contain many small scripts where the class declaration and using statements make up a significant portion of the file. Top-level statements (C# 9) allow you to write code without the traditional class wrapper, keeping small scripts focused and readable.
1// MathExtensions.cs2using UnityEngine;3 4static partial class MathExtensions5{6 public static float Remap(this float value, float from1, float to1, float from2, float to2) =>7 (value - from1) / (to1 - from1) * (to2 - from2) + from2;8 9 public static bool Approximately(this float a, float b, float tolerance = 0.001f) =>10 Mathf.Abs(a - b) < tolerance;11}When to Use Top-Level Statements
Top-level statements are ideal for:
- Utility extension methods
- Simple MonoBehaviour scripts
- Editor utility scripts
- One-liner helper functions
- Shader or material property accessors
They reduce visual noise and make small scripts easier to scan and understand at a glance.
5. Global Using Directives: Namespace Management
Solving the Repetition Problem
Large Unity projects often have many files that all need the same set of using directives--UnityEngine, UnityEngine.AI, System.Collections, and others. Global using directives (C# 10) allow you to declare these once and have them apply to all files in the project.
1// GlobalUsings.cs2global using System;3global using System.Collections;4global using System.Collections.Generic;5global using System.Linq;6global using UnityEngine;7global using UnityEngine.AI;8global using UnityEngine.Events;9global using UnityEngine.SceneManagement;6. Primary Constructors: Streamlined Initialization
C# 12's Latest Contribution
Primary constructors provide a more concise way to declare constructors and initialize properties. For Unity scripts, this can simplify initialization while maintaining compatibility with Unity's component system.
1// Traditional approach2public class Enemy : MonoBehaviour3{4 private readonly float maxHealth;5 private float currentHealth;6 private readonly string enemyType;7 8 public Enemy(float maxHealth, string enemyType)9 {10 this.maxHealth = maxHealth;11 this.currentHealth = maxHealth;12 this.enemyType = enemyType;13 }14 15 public float HealthPercent => currentHealth / maxHealth;16}17 18// With primary constructor19public class Enemy(float maxHealth, string enemyType) : MonoBehaviour20{21 public float HealthPercent => currentHealth / maxHealth;22 23 private float currentHealth = maxHealth;24}1public record CharacterClass(2 string Name,3 int BaseHealth,4 int BaseMana,5 List<string> Abilities,6 CharacterStats StartingStats7)8{9 public int EffectiveHealth => BaseHealth + StartingStats.HealthBonus;10 public int EffectiveMana => BaseMana + StartingStats.ManaBonus;11}This syntax is particularly valuable for data classes and configuration objects, service dependencies, state objects, and command and event structures.
Implementation Checklist
When modernizing your Unity codebase, consider these steps:
- Audit Existing Switch Statements - Identify opportunities to convert verbose switch blocks to expressions
- Review Type Checking Patterns - Look for repeated
ischecks andGetComponentcalls that could use pattern matching - Identify Data Models - Find classes that would benefit from record semantics
- Consolidate Using Statements - Count how many files share common using directives
- Evaluate Script Complexity - Identify simple scripts that could use top-level statements
- Plan Migration - Start with a single feature type before expanding to all six
Need guidance on implementing these practices? Our software development services can help your team adopt modern coding standards and improve overall code quality across your projects.
Best Practices for Unity Projects
Compatibility Considerations
Unity's scripting runtime version affects which C# features are available:
| Runtime Version | C# Version | Key Features Available |
|---|---|---|
| .NET Standard 2.1 | C# 8 | Switch expressions, pattern matching, readonly members |
| .NET Framework (Legacy) | Limited | Limited modern C# support |
Always test new features in a small project before large-scale adoption.
Performance Implications
Modern C# features compile to equivalent IL code--the runtime performance is nearly identical to traditional syntax. The benefits are primarily:
- Reduced code size
- Fewer potential bugs
- Better IDE support (IntelliSense, refactoring)
- Improved maintainability
Migration Strategy
When updating existing Unity projects:
- Add new features incrementally
- Use automated refactoring tools in your IDE
- Run full test suites after changes
- Document feature usage in coding standards
- Ensure team members understand new patterns
Conclusion
Modern C# features offer significant improvements for Unity development without sacrificing performance or compatibility. By adopting switch expressions, pattern matching, record types, top-level statements, global using directives, and primary constructors, you can write cleaner, more expressive code that's easier to maintain and extend.
The transition doesn't need to be all at once--start with the features that solve your most pressing code readability issues. As your team becomes comfortable with these patterns, you'll find your Unity projects becoming more maintainable and your development workflow more productive.
Need help modernizing your codebase? Our software development team specializes in helping game studios adopt modern development practices and improve code quality across their projects. From code audits to full refactoring services, we can help you achieve cleaner, more maintainable code.