Skip to main content

Modern C# in Acumatica: Refactoring for Optimal Code Clarity and Performance. Part 1.

  • November 27, 2025
  • 0 replies
  • 28 views

Forum|alt.badge.img+1

How modern C# features transform legacy Acumatica code into elegant and efficient solutions through optimization

This content was prepared by a human using a text editor, Visual Studio 2026, AI tools, and materials from open Internet sources.

If you've been working with the .NET platform and C# programming language for several years, you've likely noticed how certain development approaches become outdated, replaced by more efficient and readable solutions. C# has undergone a genuine revolution in recent years with the addition of new capabilities and syntactic enhancements. What drives this push for new features from the C# development team? I believe there are several reasons, but one is certainly the demand from us—active C# users. One quality of a good programmer is the drive to write clear, clean code. However, this aspiration isn't easy to realize. Acumatica customization developers are constrained by framework requirements and recommendations, as well as fairly rigid time constraints: the framework is designed to enable rapid development of required functionality.

The well-known GoF design patterns are cumbersome, and the minimum level where they can be applied is a business logic service, which can then be used in a graph. Here's data for the two least cumbersome GoF patterns:

Here's the minimum composition by number of classes and interfaces for two patterns: Factory Method and Template Method. This is the "minimal implementation" without additional helper classes.

Pattern Minimum Composition Count
Factory Method Abstract creator + concrete creator + product interface + concrete product 4
Template Method Abstract class + concrete class 2

Meanwhile, the majority of code in a typical customization graph is written for:

  • UI logic — field availability/visibility rules, form dynamics
  • Validations — field checks, errors, constraints, business rules
  • Data event handlers — FieldUpdated, RowSelected, RowPersisting handlers, etc.
  • Actions — buttons with logic, batch processing, long-running operations

In most cases, writing a separate service for such code is impractical—it would only complicate and bloat the codebase. On the other hand, the logic required in the cases listed above can often be quite complex, which leads to so-called code smells.

This raises a natural question—is there a solution to this problem? There is, and today I'd like to share it with the community.

It's based on a simple principle: code should be clean, readable, open to extension, and as concise as possible. In other words—minimum code to solve the task, maximum clarity for the team.

Modern C# features fit perfectly for implementing the above, while ensuring:

  • Performance: Modern features can accelerate code execution by 15-30%
  • Maintainability: Less code = fewer bugs
  • Readability: New colleagues grasp the project faster

In this article, we'll examine 16 specific C# features you can apply today in your Acumatica projects. For each feature, we'll show:

  • What it's used for and what problems it solves
  • C# code for general understanding of functionality
  • How to apply it in Acumatica projects—modern and efficient approach
  • Typical or inefficient code we've been writing for years
  • What it delivers—concrete business advantages

Whether you're working with Acumatica 2019R1 or the latest version—most of these techniques are available to you right now.

Feature List

  1. Pattern Matching — checks, validations
  2. Switch Expressions — compact switches, statuses, workflow
  3. Custom Deconstruct — deconstruction of custom classes
  4. Tuples — returning multiple values without creating DTOs
  5. Ref locals — working with variables by reference
  6. Expression-bodied members — shortening methods, properties, constructors
  7. Target-typed new — new() instead of new Type()
  8. Throw-expression — throw in expressions
  9. Using declaration — using var x = ... without a { } block
  10. Static local functions — private functions inside methods without context capture
  11. Extension methods with modern style — expression-bodied, target-typed new
  12. Local functions — functions inside methods
  13. Implicit var everywhere — var for local variables
  14. Null-coalescing improvements (????=) — defaults and null protection
  15. Tuple patterns + when guards — more complex pattern matching constructs with conditions
  16. Deconstruction + switch patterns — combining Deconstruct and pattern matching

Due to the large volume, the article had to be split into few parts. Part 1 covers features 1-5 from the list.

Let's dive in.

1. Pattern Matching

  • Introduced in: C# 7.0, .NET 4.8
  • Problem / Purpose: Allows checking types and values of objects in switch or if expressions, replacing cumbersome checks with is and casts.

C# (optimized)

/// Demonstrates pattern matching on an <see cref="object"/>.
/// If the input is an <see cref="int"/> returns "Even" or "Odd" depending on parity.
/// Returns "Unknown" for non-int inputs.
private string IsEvenOrOdd(object o) =>
o switch
{
int i when i % 2 == 0 => "Even",
int _ => "Odd",
_ => "Unknown"
};

Acumatica (optimized)

private bool IsEditable(SOOrder order) =>
order is { Hold: false, Status: SOOrderStatus.Open };

Acumatica (not optimized)

private string IsEvenOrOdd(object o)
{
if (o is int)
{
int i = (int)o;
if (i % 2 == 0) return "Even";
return "Odd";
}
return "Unknown";
}

2. Switch Expression

  • Introduced in: C# 8.0, .NET 4.8
  • Problem / Purpose: Replaces cumbersome switch/case, simplifies returning values from conditions.

C# (optimized)

/// Maps a status string to a more descriptive label using a switch expression.
/// If the status is not recognized returns "Other".
private string GetStatusLabel(string status) =>
status switch
{
"Open" => "Open for Processing",
"Hold" => "Blocked",
"Completed" => "Closed",
_ => "Other"
};

Acumatica (optimized)

private string GetStatusLabel(SOOrder o) =>
o?.Status switch
{
SOOrderStatus.Open => "Open for Processing",
SOOrderStatus.Hold => "Blocked — Review Required",
SOOrderStatus.Completed => "Shipped & Closed",
null => "No Order",
_ => "Other"
};

Acumatica (not optimized)

private string GetStatusLabel(string status)
{
switch (status)
{
case "Open": return "Open for Processing";
case "Hold": return "Blocked";
case "Completed": return "Closed";
default: return "Other";
}
}

3. Custom Deconstruct

  • Introduced in: C# 7.0, .NET 4.8
  • Problem / Purpose: Allows deconstructing objects into tuples (var a, var b) = obj, simplifying property access.

C# (optimized)

/// Simple summary class for an order with a Deconstruct method to enable deconstruction
/// into a tuple-like form: (number, total).
public class OrderSummary
{
/// <summary>Order number identifier.</summary>
public string OrderNbr { get; }
/// <summary>Total amount for the order.</summary>
public decimal Total { get; }

/// <summary>
/// Creates a new <see cref="OrderSummary"/> with the provided number and total.
/// </summary>
public OrderSummary(string orderNbr, decimal total)
{
OrderNbr = orderNbr;
Total = total;
}

/// <summary>
/// Enables deconstruction: (string number, decimal total) = orderSummary;
/// </summary>
public void Deconstruct(out string number, out decimal total)
{
number = OrderNbr;
total = Total;
}
}

private void DeconstructExamples()
{
OrderSummary order = new OrderSummary("ORD001", 150.5m);
var (number, total) = order;
Console.WriteLine($"Order {number} has total {total}");

// ----------------- 2) Explicit types -----------------
string num;
decimal tot;
order.Deconstruct(out num, out tot);
Console.WriteLine($"Order {num} has total {tot}");

// ----------------- 3) Using in switch pattern matching -----------------
string status = order switch {
OrderSummary(var n, var t) when t > 100 => $"High value order {n}",
OrderSummary(var n, var t) => $"Order {n} with total {t}",
null => "No order"
};
Console.WriteLine(status);

// ----------------- 4) Returning from method with deconstruction -----------------
var order2 = CreateOrder("ORD002", 50m);
var (num3, tot3) = order2;
Console.WriteLine($"Created order {num3}, total {tot3}");

// ----------------- 5) Passing to method via deconstruction -----------------
PrintOrder(order);
PrintOrder(order2);

// ----------------- 6) Using with tuple -----------------
var orderTuple = (order, order2);
var ((n1, t1), (n2, t2)) = orderTuple;
Console.WriteLine($"Orders combined: {n1}-{t1} & {n2}-{t2}");

// ----------------- 7) Deconstruction directly in method arguments -----------------
PrintOrderTuple((order, order2));

#region Local functions
// Method returning OrderSummary
static OrderSummary CreateOrder(string nbr, decimal total)
{
return new OrderSummary(nbr, total);
}

// Method accepting deconstruction via tuple
static void PrintOrder(OrderSummary order)
{
var (nbr, total) = order;
Console.WriteLine($"PrintOrder: {nbr} - {total}");
}

// Method accepting tuple of two OrderSummary
static void PrintOrderTuple((OrderSummary first, OrderSummary second) orders)
{
var ((n1, t1), (n2, t2)) = orders;
Console.WriteLine($"PrintOrderTuple: {n1}-{t1} & {n2}-{t2}");
}
#endregion
}

Acumatica (optimized)

// Code is analogous to the C# new style example

Acumatica (not optimized)

public void DeconstructExamples()
{
PXTrace.WriteInformation("=== Old-Style Example (no Deconstruct) ===");

// ----------------- 1) Object creation -----------------
OrderSummary order = new OrderSummary("ORD100", 999.99m);

// ----------------- 2) Assigning properties to variables -----------------
string number = order.OrderNbr;
decimal total = order.Total;
PXTrace.WriteInformation("Order " + number + " has total " + total);

// ----------------- 3) Using with explicit types -----------------
string num2 = order.OrderNbr;
decimal tot2 = order.Total;
PXTrace.WriteInformation("Explicit: " + num2 + " - " + tot2);

// ----------------- 4) Using in if instead of switch pattern matching -----------------
string message;
if (order.Total > 500)
{
message = "High-value order " + order.OrderNbr + ": " + order.Total;
}
else
{
message = "Order " + order.OrderNbr + " total " + order.Total;
}
PXTrace.WriteInformation(message);

// ----------------- 5) Passing to method -----------------
PrintOrder(order);
}

// ----------------- Method for demonstration -----------------
private void PrintOrder(OrderSummary order)
{
string nbr = order.OrderNbr;
decimal total = order.Total;
PXTrace.WriteInformation("PrintOrder: " + nbr + " - " + total);
}

// ----------------- OrderSummary class without Deconstruct -----------------
public class OrderSummary
{
public string OrderNbr;
public decimal Total;

// Constructor
public OrderSummary(string orderNbr, decimal total)
{
OrderNbr = orderNbr;
Total = total;
}

// No Deconstruct in old style
}

4. Tuples

  • Introduced in: C# 7.0, .NET 4.8
  • Problem / Purpose: Allows returning multiple values from a method without creating a separate class.

C# (optimized)

private (decimal subtotal, decimal tax, decimal total) CalcTotals(decimal total, decimal tax)
{
var subtotal = total - tax;
return (subtotal, tax, total);
}

Acumatica (optimized)

private (decimal subtotal, decimal tax, decimal total) CalcTotals(SOOrder o)
{
var subtotal = (o.OrderTotal ?? 0m) - (o.TaxTotal ?? 0m);
var tax = o.TaxTotal ?? 0m;
return (subtotal, tax, subtotal + tax);
}

Acumatica (not optimized)

public struct Totals
{
public decimal Subtotal;
public decimal Tax;
public decimal Total;
}
private Totals CalcTotals(decimal total, decimal tax)
{
Totals t;
t.Subtotal = total - tax;
t.Tax = tax;
t.Total = total;
return t;
}

5. Ref locals

  • Introduced in: C# 7.0, .NET 4.8
  • Problem / Purpose: Allows working with variables by reference, modifying them directly.

C# (optimized)

private void Increment()
{
int qty = 10;
ref int reference = ref qty;
reference++;
Console.WriteLine($"Qty updated = {qty}");
}

Acumatica (optimized)

private void Increment()
{
int qty = 10;
ref int reference = ref qty;
reference++;
PXTrace.WriteInformation($"Qty updated = {qty}");
}

Acumatica (not optimized)

private void Increment()
{
int qty = 10;
int reference = qty;
reference++;
qty = reference;
PXTrace.WriteInformation("Qty = " + qty);
}

These five tools are a simple way to make code cleaner and shorter even within .NET 4.8. They don't require architectural changes, integrate easily into existing customizations, and address the most common routine tasks: validations, data parsing, returning multiple values, and optimizing reference handling. That concludes Part 1—next we'll explore additional features that deliver even more practical benefits in Acumatica.

Continued in the next part. Stay tuned!